diff --git a/init.db b/init.db deleted file mode 100644 index e69de29..0000000 diff --git a/readme.md b/readme.md index 612f1aa..e44d024 100644 --- a/readme.md +++ b/readme.md @@ -46,7 +46,8 @@ See `/examples` for more. - SQLite integration - Built-in JSON library - Static file serving -- Request validator library +- Request validator library +- 'Hot Restart' mechanism ## In Progress diff --git a/src/cli.c b/src/cli.c index 35d9da1..ca71847 100644 --- a/src/cli.c +++ b/src/cli.c @@ -33,7 +33,7 @@ int runProject() { } int buildProject() { - return system("make"); + return system("make -s"); } // migrations are being deferred because that sounds like a PAIN IN THE ASS diff --git a/src/lavandula.c b/src/lavandula.c index 482e009..0adefd8 100644 --- a/src/lavandula.c +++ b/src/lavandula.c @@ -28,7 +28,7 @@ AppBuilder createBuilder() { App createApp() { App app = { - .port = 3000 + .port = 3000, }; initAppMiddleware(&app); diff --git a/src/server.c b/src/server.c index 5641786..d162682 100644 --- a/src/server.c +++ b/src/server.c @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include "include/server.h" #include "include/http.h" @@ -13,8 +16,26 @@ #include "include/sql.h" #include "include/app.h" +typedef enum { + STATE_RUNNING, + STATE_RESTARTING, + STATE_SHUTDOWN +} ServerState; + +volatile ServerState serverState = STATE_RUNNING; + #define BUFFER_SIZE 4096 +void set_nonblocking_input() { + struct termios ttystate; + + tcgetattr(STDIN_FILENO, &ttystate); + ttystate.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &ttystate); + + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); +} + HttpResponse defaultNotFoundController(RequestContext context) { (void)context; return (HttpResponse) { @@ -52,9 +73,39 @@ void freeServer(Server *server) { freeRouter(&server->router); } +void* key_listener(void* arg) { + (void)arg; + + while (serverState == STATE_RUNNING) { + int ch = getchar(); + if (ch != EOF) { + if (ch == 'r') { + printf("restarting server...\n"); + serverState = STATE_RESTARTING; + return NULL; + } else if (ch == 'q') { + printf("shutting down server...\n"); + serverState = STATE_SHUTDOWN; + return NULL; + } + } + usleep(10000); + } + return NULL; +} + + void runServer(App *app) { if (!app) return; + pthread_t thread_id; + set_nonblocking_input(); + + if (pthread_create(&thread_id, NULL, key_listener, NULL)) { + perror("Failed to create thread"); + return; + } + app->server.fileDescriptor = socket(AF_INET, SOCK_STREAM, 0); if (app->server.fileDescriptor < 0) { perror("socket failed"); @@ -87,16 +138,41 @@ void runServer(App *app) { exit(EXIT_FAILURE); } - printf("Lavandula Server is running! -> http://127.0.0.1:%d\n", app->server.port); + printf("\n"); + printf("┌───────────────────────────────────────────────┐\n"); + printf("│ 🌿 Lavandula Server is RUNNING │\n"); + printf("├───────────────────────────────────────────────┤\n"); + printf("│ Listening on: http://127.0.0.1:%-12d │\n", app->server.port); + printf("│ │\n"); + printf("│ Controls: │\n"); + printf("│ • Press 'r' to reload the server │\n"); + printf("│ • Press 'q' to shut down │\n"); + printf("└───────────────────────────────────────────────┘\n\n"); + + + int flags = fcntl(app->server.fileDescriptor, F_GETFL, 0); + if (flags == -1) { + perror("fcntl get flags failed"); + exit(EXIT_FAILURE); + } + if (fcntl(app->server.fileDescriptor, F_SETFL, flags | O_NONBLOCK) == -1) { + perror("fcntl set non-blocking failed"); + exit(EXIT_FAILURE); + } - while (1) { + while (serverState == STATE_RUNNING) { struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSocket = accept(app->server.fileDescriptor, (struct sockaddr *)&clientAddr, &clientLen); if (clientSocket < 0) { - perror("accept failed"); - continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) { + usleep(10000); + continue; + } else { + perror("accept failed"); + continue; + } } char buffer[BUFFER_SIZE] = {0}; // oops @@ -129,7 +205,6 @@ void runServer(App *app) { free(pathOnly); - RequestContext context = requestContext(app, request); context.hasBody = parser.isValid && request.bodyLength > 0; @@ -180,4 +255,22 @@ void runServer(App *app) { close(clientSocket); } -} + + freeServer(&app->server); + pthread_join(thread_id, NULL); + + if (serverState == STATE_RESTARTING) { + int result = system("make -s"); + if (result != 0) { + fprintf(stderr, "Build failed.\n"); + exit(1); + } + + char *args[] = {"./build/lavu", NULL}; + execvp(args[0], args); + perror("execvp failed"); + exit(1); + } else if (serverState == STATE_SHUTDOWN) { + exit(0); + } +} \ No newline at end of file