From 34374a145396c4cb96842f98803223ecfb36080c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20B=C3=A9rub=C3=A9?= Date: Tue, 10 Sep 2024 12:44:23 -0400 Subject: [PATCH] Reorganizing the HTTP server for eventual multiprocessing improvements --- README.md | 5 +- src/night.c | 10 +- src/server.c | 1286 +++++++++++++++++++++++++------------------------- 3 files changed, 651 insertions(+), 650 deletions(-) diff --git a/README.md b/README.md index 3325494..38cad35 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,12 @@ _* At the moment, only text, 24-bit and 32-bit RGB overlays are handled, matrici ### Roadmap -- [ ] Audio adjustments (source, input gain, output volume) +- [ ] Audio source, input gain and output volume - [ ] Motors and PTZ control - [ ] ONVIF services - [ ] Additional WebUI functionalities -- [ ] Motion detection reimplementation +- [ ] Lens correction profiles +- [ ] Local recordings with motion detection - [ ] Alternative audio codecs diff --git a/src/night.c b/src/night.c index 26ce90f..a39506a 100644 --- a/src/night.c +++ b/src/night.c @@ -50,24 +50,18 @@ void *night_thread(void) { HAL_DANGER("night", "Could not open the ADC virtual device!\n"); return NULL; } - struct timeval tv = { - .tv_sec = app_config.check_interval_s % 12, - .tv_usec = app_config.check_interval_s / 12 * 1000000 }; while (keepRunning) { - FD_ZERO(&adc_fds); - FD_SET(adc_fd, &adc_fds); - select(adc_fd + 1, &adc_fds, NULL, NULL, &tv); if (read(adc_fd, &val, sizeof(val)) > 0) { usleep(10000); tmp += val; + cnt++; } - cnt++; if (cnt == 12) { tmp /= cnt; set_night_mode(tmp >= app_config.adc_threshold); cnt = tmp = 0; } - usleep(250000); + usleep(app_config.check_interval_s * 1000000 / 12); } if (adc_fd) close(adc_fd); } else if (app_config.ir_sensor_pin == 999) { diff --git a/src/server.c b/src/server.c index 962bfa7..0a0236d 100644 --- a/src/server.c +++ b/src/server.c @@ -15,25 +15,26 @@ enum StreamType { STREAM_PCM }; +struct Request { + int clntFd; + char *input, *method, *payload, *prot, *query, *uri; + int paysize, total; +}; + struct Client { int socket_fd; enum StreamType type; struct Mp4State mp4; unsigned int nalCnt; -}; - -struct Client client_fds[MAX_CLIENTS]; -pthread_mutex_t client_fds_mutex; +} client_fds[MAX_CLIENTS]; -char response[256]; -char *method, *payload, *prot, *request, *query, *uri; -int paysize, received, total = 0; - -typedef struct { +struct Header { char *name, *value; -} header_t; +} reqhdr[17] = {{"\0", "\0"}}; -header_t reqhdr[17] = {{"\0", "\0"}}; +int server_fd = -1; +pthread_t server_thread_id; +pthread_mutex_t client_fds_mutex; const char error400[] = "HTTP/1.1 400 Bad Request\r\n" \ "Content-Type: text/plain\r\n" \ @@ -84,7 +85,6 @@ int send_to_fd_nonblock(int client_fd, char *buf, ssize_t size) { } int send_to_client(int i, char *buf, ssize_t size) { - ; if (send_to_fd(client_fds[i].socket_fd, buf, size) < 0) { free_client(i); return -1; @@ -334,17 +334,18 @@ void *send_jpeg_thread(void *vargp) { if (ret) { HAL_DANGER("server", "Failed to receive a JPEG snapshot...\n"); static char response[] = - "HTTP/1.1 503 Internal Error\r\nContent-Length: 11\r\nConnection: " - "close\r\n\r\nHello, 503!"; + "HTTP/1.1 503 Internal Error\r\n" + "Connection: close\r\n\r\n"; send_and_close(task.client_fd, response, sizeof(response) - 1); // zero ending string! return NULL; } HAL_INFO("server", "JPEG snapshot has been received!\n"); char buf[1024]; int buf_len = sprintf( - buf, - "HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\nContent-Length: " - "%lu\r\nConnection: close\r\n\r\n", + buf, "HTTP/1.1 200 OK\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %lu\r\n" + "Connection: close\r\n\r\n", jpeg.jpegSize); send_to_fd(task.client_fd, buf, buf_len); send_to_fd(task.client_fd, jpeg.data, jpeg.jpegSize); @@ -356,7 +357,7 @@ void *send_jpeg_thread(void *vargp) { } int send_file(const int client_fd, const char *path) { - if (access(path, F_OK) != -1) { + if (!access(path, F_OK)) { const char *mime = (path); FILE *file = fopen(path, "r"); if (file == NULL) { @@ -365,9 +366,10 @@ int send_file(const int client_fd, const char *path) { } char header[1024]; int header_len = sprintf( - header, - "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nTransfer-Encoding: " - "chunked\r\nConnection: keep-alive\r\n\r\n", + header, "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Transfer-Encoding: chunked\r\n" + "Connection: keep-alive\r\n\r\n", mime); send_to_fd(client_fd, header, header_len); // zero ending string! const int buf_size = 1024; @@ -376,9 +378,8 @@ int send_file(const int client_fd, const char *path) { ssize_t len_size; while (1) { ssize_t size = fread(buf, sizeof(char), buf_size, file); - if (size <= 0) { + if (size <= 0) break; - } len_size = sprintf(len_buf, "%zX\r\n", size); buf[size++] = '\r'; buf[size++] = '\n'; @@ -401,7 +402,7 @@ void send_html(const int client_fd, const char *data) { &buf, "HTTP/1.1 200 OK\r\n" \ "Content-Type: text/html\r\n" \ - "Content-Length: %ld\r\n" \ + "Content-Length: %zu\r\n" \ "Connection: close\r\n" \ "\r\n%s", strlen(data), data); @@ -412,44 +413,53 @@ void send_html(const int client_fd, const char *data) { char *request_header(const char *name) { - header_t *h = reqhdr; + struct Header *h = reqhdr; for (; h->name; h++) if (!strcasecmp(h->name, name)) return h->value; return NULL; } -header_t *request_headers(void) { return reqhdr; } +struct Header *request_headers(void) { return reqhdr; } -void parse_request(int client_fd, char *request) { +void parse_request(struct Request *req) { struct sockaddr_in client_sock; socklen_t client_sock_len = sizeof(client_sock); memset(&client_sock, 0, client_sock_len); - getpeername(client_fd, + req->total = 0; + int received = recv(req->clntFd, req->input, REQSIZE, 0); + if (received < 0) + HAL_WARNING("server", "recv() error\n"); + else if (!received) + HAL_WARNING("server", "Client disconnected unexpectedly\n"); + req->total += received; + + if (req->total <= 0) return; + + getpeername(req->clntFd, (struct sockaddr *)&client_sock, &client_sock_len); char *state = NULL; - method = strtok_r(request, " \t\r\n", &state); - uri = strtok_r(NULL, " \t", &state); - prot = strtok_r(NULL, " \t\r\n", &state); + req->method = strtok_r(req->input, " \t\r\n", &state); + req->uri = strtok_r(NULL, " \t", &state); + req->prot = strtok_r(NULL, " \t\r\n", &state); HAL_INFO("server", "\x1b[32mNew request: (%s) %s\n" " Received from: %s\x1b[0m\n", - method, uri, inet_ntoa(client_sock.sin_addr)); + req->method, req->uri, inet_ntoa(client_sock.sin_addr)); - if (query = strchr(uri, '?')) - *query++ = '\0'; + if (req->query = strchr(req->uri, '?')) + *req->query++ = '\0'; else - query = uri - 1; + req->query = req->uri - 1; - header_t *h = reqhdr; + struct Header *h = reqhdr; char *l; while (h < reqhdr + 16) { char *k, *v, *e; - k = strtok_r(NULL, "\r\n: \t", &state); - if (!k) + if (!(k = strtok_r(NULL, "\r\n: \t", &state))) break; v = strtok_r(NULL, "\r\n", &state); while (*v && *v == ' ' && v++); @@ -464,10 +474,10 @@ void parse_request(int client_fd, char *request) { } l = request_header("Content-Length"); - paysize = l ? atol(l) : 0; + req->paysize = l ? atol(l) : 0; - while (l && total < paysize) { - received = recv(client_fd, request + total, REQSIZE - total, 0); + while (l && req->total < req->paysize) { + received = recv(req->clntFd, req->input + req->total, REQSIZE - req->total, 0); if (received < 0) { HAL_WARNING("server", "recv() error\n"); break; @@ -475,685 +485,681 @@ void parse_request(int client_fd, char *request) { HAL_WARNING("server", "Client disconnected unexpectedly\n"); break; } - total += received; + req->total += received; } - payload = strtok_r(NULL, "\r\n", &state); + req->payload = strtok_r(NULL, "\r\n", &state); } -void *server_thread(void *vargp) { - int ret, server_fd = *((int *)vargp); - int enable = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { - HAL_WARNING("server", "setsockopt(SO_REUSEADDR) failed"); - fflush(stdout); - } - struct sockaddr_in server, client; - server.sin_family = AF_INET; - server.sin_port = htons(app_config.web_port); - server.sin_addr.s_addr = htonl(INADDR_ANY); - if (ret = bind(server_fd, (struct sockaddr *)&server, sizeof(server))) { - HAL_DANGER("server", "%s (%d)\n", strerror(errno), errno); - keepRunning = 0; - close_socket_fd(server_fd); - return NULL; - } - listen(server_fd, 128); - - request = malloc(REQSIZE); +void respond_request(struct Request *req) { + char response[256]; - while (keepRunning) { - // waiting for a new connection - int client_fd = accept(server_fd, NULL, NULL); - if (client_fd == -1) - break; - - total = 0; - received = recv(client_fd, request, REQSIZE, 0); - if (received < 0) - HAL_WARNING("server", "recv() error\n"); - else if (!received) - HAL_WARNING("server", "Client disconnected unexpectedly\n"); - total += received; + if (!EQUALS(req->method, "GET") && !EQUALS(req->method, "POST")) { + send_and_close(req->clntFd, (char*)error405, strlen(error405)); + return; + } - if (total <= 0) continue; + if (app_config.web_enable_auth) { + char *auth = request_header("Authorization"); + char cred[65], valid[256]; - parse_request(client_fd, request); + strcpy(cred, app_config.web_auth_user); + strcpy(cred + strlen(app_config.web_auth_user), ":"); + strcpy(cred + strlen(app_config.web_auth_user) + 1, app_config.web_auth_pass); + strcpy(valid, "Basic "); + base64_encode(valid + 6, cred, strlen(cred)); - if (!EQUALS(method, "GET") && !EQUALS(method, "POST")) { - send_and_close(client_fd, (char*)error405, strlen(error405)); - continue; + if (!auth || !EQUALS(auth, valid)) { + int respLen = sprintf(response, + "HTTP/1.1 401 Unauthorized\r\n" + "Content-Type: text/plain\r\n" + "WWW-Authenticate: Basic realm=\"Access the camera services\"\r\n" + "Connection: close\r\n\r\n" + ); + send_and_close(req->clntFd, response, respLen); + return; } + } - if (app_config.web_enable_auth) { - char *auth = request_header("Authorization"); - char cred[65], valid[256]; - - strcpy(cred, app_config.web_auth_user); - strcpy(cred + strlen(app_config.web_auth_user), ":"); - strcpy(cred + strlen(app_config.web_auth_user) + 1, app_config.web_auth_pass); - strcpy(valid, "Basic "); - base64_encode(valid + 6, cred, strlen(cred)); - - if (!auth || !EQUALS(auth, valid)) { - int respLen = sprintf(response, - "HTTP/1.1 401 Unauthorized\r\n" \ - "Content-Type: text/plain\r\n" \ - "WWW-Authenticate: Basic realm=\"Access the camera services\"\r\n" \ + if (EQUALS(req->uri, "/exit")) { + int respLen = sprintf( + response, "HTTP/1.1 200 OK\r\n" "Connection: close\r\n\r\n" - ); - send_and_close(client_fd, response, respLen); - continue; - } - } - - if (EQUALS(uri, "/exit")) { - int respLen = sprintf( - response, "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n" - "Connection: close\r\n\r\nClosing..."); - send_and_close(client_fd, response, respLen); - keepRunning = 0; - break; - } - - if (EQUALS(uri, "/") || EQUALS(uri, "/index.htm") || EQUALS(uri, "/index.html")) { - send_html(client_fd, indexhtml); - continue; - } - - if (app_config.audio_enable && EQUALS(uri, "/audio.mp3")) { - int respLen = sprintf( - response, "HTTP/1.1 200 OK\r\nContent-Type: " - "audio/mpeg\r\nTransfer-Encoding: " - "chunked\r\nConnection: keep-alive\r\n\r\n"); - send_to_fd(client_fd, response, respLen); - pthread_mutex_lock(&client_fds_mutex); - for (uint32_t i = 0; i < MAX_CLIENTS; ++i) - if (client_fds[i].socket_fd < 0) { - client_fds[i].socket_fd = client_fd; - client_fds[i].type = STREAM_MP3; - break; - } - pthread_mutex_unlock(&client_fds_mutex); - continue; - } + "Closing..."); + send_and_close(req->clntFd, response, respLen); + keepRunning = 0; + graceful = 1; + return; + } - if (app_config.audio_enable && EQUALS(uri, "/audio.pcm")) { - int respLen = sprintf( - response, "HTTP/1.1 200 OK\r\nContent-Type: " - "audio/pcm\r\nTransfer-Encoding: " - "chunked\r\nConnection: keep-alive\r\n\r\n"); - send_to_fd(client_fd, response, respLen); - pthread_mutex_lock(&client_fds_mutex); - for (uint32_t i = 0; i < MAX_CLIENTS; ++i) - if (client_fds[i].socket_fd < 0) { - client_fds[i].socket_fd = client_fd; - client_fds[i].type = STREAM_PCM; - break; - } - pthread_mutex_unlock(&client_fds_mutex); - continue; - } + if (EQUALS(req->uri, "/") || EQUALS(req->uri, "/index.htm") || EQUALS(req->uri, "/index.html")) { + send_html(req->clntFd, indexhtml); + return; + } - if ((!app_config.mp4_codecH265 && EQUALS(uri, "/video.264")) || - (app_config.mp4_codecH265 && EQUALS(uri, "/video.265"))) { - request_idr(); - int respLen = sprintf( - response, "HTTP/1.1 200 OK\r\nContent-Type: " - "application/octet-stream\r\nTransfer-Encoding: " - "chunked\r\nConnection: keep-alive\r\n\r\n"); - send_to_fd(client_fd, response, respLen); - pthread_mutex_lock(&client_fds_mutex); - for (uint32_t i = 0; i < MAX_CLIENTS; ++i) - if (client_fds[i].socket_fd < 0) { - client_fds[i].socket_fd = client_fd; - client_fds[i].type = STREAM_H26X; - client_fds[i].nalCnt = 0; - break; - } - pthread_mutex_unlock(&client_fds_mutex); - continue; - } + if (app_config.audio_enable && EQUALS(req->uri, "/audio.mp3")) { + int respLen = sprintf( + response, "HTTP/1.1 200 OK\r\nContent-Type: " + "audio/mpeg\r\nTransfer-Encoding: " + "chunked\r\nConnection: keep-alive\r\n\r\n"); + send_to_fd(req->clntFd, response, respLen); + pthread_mutex_lock(&client_fds_mutex); + for (uint32_t i = 0; i < MAX_CLIENTS; ++i) + if (client_fds[i].socket_fd < 0) { + client_fds[i].socket_fd = req->clntFd; + client_fds[i].type = STREAM_MP3; + break; + } + pthread_mutex_unlock(&client_fds_mutex); + return; + } - if (app_config.mp4_enable && EQUALS(uri, "/video.mp4")) { - request_idr(); - int respLen = sprintf( - response, "HTTP/1.1 200 OK\r\nContent-Type: " - "video/mp4\r\nTransfer-Encoding: chunked\r\n" - "Connection: keep-alive\r\n\r\n"); - send_to_fd(client_fd, response, respLen); - pthread_mutex_lock(&client_fds_mutex); - for (uint32_t i = 0; i < MAX_CLIENTS; ++i) - if (client_fds[i].socket_fd < 0) { - client_fds[i].socket_fd = client_fd; - client_fds[i].type = STREAM_MP4; - client_fds[i].mp4.header_sent = false; - break; - } - pthread_mutex_unlock(&client_fds_mutex); - continue; - } + if (app_config.audio_enable && EQUALS(req->uri, "/audio.pcm")) { + int respLen = sprintf( + response, "HTTP/1.1 200 OK\r\nContent-Type: " + "audio/pcm\r\nTransfer-Encoding: " + "chunked\r\nConnection: keep-alive\r\n\r\n"); + send_to_fd(req->clntFd, response, respLen); + pthread_mutex_lock(&client_fds_mutex); + for (uint32_t i = 0; i < MAX_CLIENTS; ++i) + if (client_fds[i].socket_fd < 0) { + client_fds[i].socket_fd = req->clntFd; + client_fds[i].type = STREAM_PCM; + break; + } + pthread_mutex_unlock(&client_fds_mutex); + return; + } - if (app_config.mjpeg_enable && EQUALS(uri, "/mjpeg")) { - int respLen = sprintf( - response, "HTTP/1.0 200 OK\r\nCache-Control: no-cache\r\n" - "Pragma: no-cache\r\nConnection: close\r\n" - "Content-Type: multipart/x-mixed-replace; " - "boundary=boundarydonotcross\r\n\r\n"); - send_to_fd(client_fd, response, respLen); - pthread_mutex_lock(&client_fds_mutex); - for (uint32_t i = 0; i < MAX_CLIENTS; ++i) - if (client_fds[i].socket_fd < 0) { - client_fds[i].socket_fd = client_fd; - client_fds[i].type = STREAM_MJPEG; - break; - } - pthread_mutex_unlock(&client_fds_mutex); - continue; - } + if ((!app_config.mp4_codecH265 && EQUALS(req->uri, "/video.264")) || + (app_config.mp4_codecH265 && EQUALS(req->uri, "/video.265"))) { + request_idr(); + int respLen = sprintf( + response, "HTTP/1.1 200 OK\r\nContent-Type: " + "application/octet-stream\r\nTransfer-Encoding: " + "chunked\r\nConnection: keep-alive\r\n\r\n"); + send_to_fd(req->clntFd, response, respLen); + pthread_mutex_lock(&client_fds_mutex); + for (uint32_t i = 0; i < MAX_CLIENTS; ++i) + if (client_fds[i].socket_fd < 0) { + client_fds[i].socket_fd = req->clntFd; + client_fds[i].type = STREAM_H26X; + client_fds[i].nalCnt = 0; + break; + } + pthread_mutex_unlock(&client_fds_mutex); + return; + } - if (app_config.jpeg_enable && STARTS_WITH(uri, "/image.jpg")) { - { - struct jpegtask task; - task.client_fd = client_fd; - task.width = app_config.jpeg_width; - task.height = app_config.jpeg_height; - task.qfactor = app_config.jpeg_qfactor; - task.color2Gray = 0; - - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "width")) { - short result = strtol(value, &remain, 10); - if (remain != value) - task.width = result; - } - else if (EQUALS(key, "height")) { - short result = strtol(value, &remain, 10); - if (remain != value) - task.height = result; - } - else if (EQUALS(key, "qfactor")) { - short result = strtol(value, &remain, 10); - if (remain != value) - task.qfactor = result; - } - else if (EQUALS(key, "color2gray") || EQUALS(key, "gray")) { - if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) - task.color2Gray = 1; - else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) - task.color2Gray = 0; - } - } - } + if (app_config.mp4_enable && EQUALS(req->uri, "/video.mp4")) { + request_idr(); + int respLen = sprintf( + response, "HTTP/1.1 200 OK\r\nContent-Type: " + "video/mp4\r\nTransfer-Encoding: chunked\r\n" + "Connection: keep-alive\r\n\r\n"); + send_to_fd(req->clntFd, response, respLen); + pthread_mutex_lock(&client_fds_mutex); + for (uint32_t i = 0; i < MAX_CLIENTS; ++i) + if (client_fds[i].socket_fd < 0) { + client_fds[i].socket_fd = req->clntFd; + client_fds[i].type = STREAM_MP4; + client_fds[i].mp4.header_sent = false; + break; + } + pthread_mutex_unlock(&client_fds_mutex); + return; + } - pthread_t thread_id; - pthread_attr_t thread_attr; - pthread_attr_init(&thread_attr); - size_t stacksize; - pthread_attr_getstacksize(&thread_attr, &stacksize); - size_t new_stacksize = 16 * 1024; - if (pthread_attr_setstacksize(&thread_attr, new_stacksize)) - HAL_DANGER("jpeg", "Can't set stack size %zu\n", new_stacksize); - pthread_create( - &thread_id, &thread_attr, send_jpeg_thread, (void *)&task); - if (pthread_attr_setstacksize(&thread_attr, stacksize)) - HAL_DANGER("jpeg", "Can't set stack size %zu\n", stacksize); - pthread_attr_destroy(&thread_attr); + if (app_config.mjpeg_enable && EQUALS(req->uri, "/mjpeg")) { + int respLen = sprintf( + response, "HTTP/1.0 200 OK\r\nCache-Control: no-cache\r\n" + "Pragma: no-cache\r\nConnection: close\r\n" + "Content-Type: multipart/x-mixed-replace; " + "boundary=boundarydonotcross\r\n\r\n"); + send_to_fd(req->clntFd, response, respLen); + pthread_mutex_lock(&client_fds_mutex); + for (uint32_t i = 0; i < MAX_CLIENTS; ++i) + if (client_fds[i].socket_fd < 0) { + client_fds[i].socket_fd = req->clntFd; + client_fds[i].type = STREAM_MJPEG; + break; } - continue; - } + pthread_mutex_unlock(&client_fds_mutex); + return; + } + + if (app_config.jpeg_enable && STARTS_WITH(req->uri, "/image.jpg")) { + { + struct jpegtask task; + task.client_fd = req->clntFd; + task.width = app_config.jpeg_width; + task.height = app_config.jpeg_height; + task.qfactor = app_config.jpeg_qfactor; + task.color2Gray = 0; - if (app_config.audio_enable && EQUALS(uri, "/api/audio")) { - if (!EMPTY(query)) { + if (!EMPTY(req->query)) { char *remain; - while (query) { - char *value = split(&query, "&"); + while (req->query) { + char *value = split(&req->query, "&"); if (!value || !*value) continue; - unescape_uri(value); char *key = split(&value, "="); if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "bitrate")) { + if (EQUALS(key, "width")) { + short result = strtol(value, &remain, 10); + if (remain != value) + task.width = result; + } + else if (EQUALS(key, "height")) { short result = strtol(value, &remain, 10); if (remain != value) - app_config.audio_bitrate = result; - } else if (EQUALS(key, "srate")) { + task.height = result; + } + else if (EQUALS(key, "qfactor")) { short result = strtol(value, &remain, 10); if (remain != value) - app_config.audio_srate = result; + task.qfactor = result; + } + else if (EQUALS(key, "color2gray") || EQUALS(key, "gray")) { + if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) + task.color2Gray = 1; + else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) + task.color2Gray = 0; } } - - disable_audio(); - enable_audio(); } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"bitrate\":%d,\"srate\":%d}", - app_config.audio_bitrate, app_config.audio_srate); - send_and_close(client_fd, response, respLen); - continue; + pthread_t thread_id; + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + size_t stacksize; + pthread_attr_getstacksize(&thread_attr, &stacksize); + size_t new_stacksize = 16 * 1024; + if (pthread_attr_setstacksize(&thread_attr, new_stacksize)) + HAL_DANGER("jpeg", "Can't set stack size %zu\n", new_stacksize); + pthread_create( + &thread_id, &thread_attr, send_jpeg_thread, (void *)&task); + if (pthread_attr_setstacksize(&thread_attr, stacksize)) + HAL_DANGER("jpeg", "Can't set stack size %zu\n", stacksize); + pthread_attr_destroy(&thread_attr); } + return; + } - if (EQUALS(uri, "/api/cmd")) { - int result = -1; - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key) continue; - if (EQUALS(key, "save")) { - result = save_app_config(); - if (!result) - HAL_INFO("server", "Configuration saved!\n"); - else - HAL_WARNING("server", "Failed to save configuration!\n"); - break; - } + if (app_config.audio_enable && EQUALS(req->uri, "/api/audio")) { + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "bitrate")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.audio_bitrate = result; + } else if (EQUALS(key, "srate")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.audio_srate = result; } } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"code\":%d}", result); - send_and_close(client_fd, response, respLen); - continue; + disable_audio(); + enable_audio(); } - if (EQUALS(uri, "/api/jpeg")) { - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "width")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.jpeg_width = result; - } else if (EQUALS(key, "height")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.jpeg_height = result; - } else if (EQUALS(key, "qfactor")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.jpeg_qfactor = result; - } + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"bitrate\":%d,\"srate\":%d}", + app_config.audio_bitrate, app_config.audio_srate); + send_and_close(req->clntFd, response, respLen); + return; + } + + if (EQUALS(req->uri, "/api/cmd")) { + int result = -1; + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key) continue; + if (EQUALS(key, "save")) { + result = save_app_config(); + if (!result) + HAL_INFO("server", "Configuration saved!\n"); + else + HAL_WARNING("server", "Failed to save configuration!\n"); + break; } + } + } - jpeg_deinit(); - if (app_config.jpeg_enable) jpeg_init(); + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"code\":%d}", result); + send_and_close(req->clntFd, response, respLen); + return; + } + + if (EQUALS(req->uri, "/api/jpeg")) { + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "width")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.jpeg_width = result; + } else if (EQUALS(key, "height")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.jpeg_height = result; + } else if (EQUALS(key, "qfactor")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.jpeg_qfactor = result; + } } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"width\":%d,\"height\":%d,\"qfactor\":%d}", - app_config.jpeg_width, app_config.jpeg_height, app_config.jpeg_qfactor); - send_and_close(client_fd, response, respLen); - continue; + jpeg_deinit(); + if (app_config.jpeg_enable) jpeg_init(); } - if (EQUALS(uri, "/api/mjpeg")) { - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "width")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mjpeg_width = result; - } else if (EQUALS(key, "height")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mjpeg_height = result; - } else if (EQUALS(key, "fps")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mjpeg_fps = result; - } else if (EQUALS(key, "mode")) { - if (EQUALS_CASE(value, "CBR")) - app_config.mjpeg_mode = HAL_VIDMODE_CBR; - else if (EQUALS_CASE(value, "VBR")) - app_config.mjpeg_mode = HAL_VIDMODE_VBR; - else if (EQUALS_CASE(value, "QP")) - app_config.mjpeg_mode = HAL_VIDMODE_QP; - } - } + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"width\":%d,\"height\":%d,\"qfactor\":%d}", + app_config.jpeg_width, app_config.jpeg_height, app_config.jpeg_qfactor); + send_and_close(req->clntFd, response, respLen); + return; + } - disable_mjpeg(); - if (app_config.mjpeg_enable) enable_mjpeg(); + if (EQUALS(req->uri, "/api/mjpeg")) { + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "width")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mjpeg_width = result; + } else if (EQUALS(key, "height")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mjpeg_height = result; + } else if (EQUALS(key, "fps")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mjpeg_fps = result; + } else if (EQUALS(key, "mode")) { + if (EQUALS_CASE(value, "CBR")) + app_config.mjpeg_mode = HAL_VIDMODE_CBR; + else if (EQUALS_CASE(value, "VBR")) + app_config.mjpeg_mode = HAL_VIDMODE_VBR; + else if (EQUALS_CASE(value, "QP")) + app_config.mjpeg_mode = HAL_VIDMODE_QP; + } } - char mode[5] = "\0"; - switch (app_config.mjpeg_mode) { - case HAL_VIDMODE_CBR: strcpy(mode, "CBR"); break; - case HAL_VIDMODE_VBR: strcpy(mode, "VBR"); break; - case HAL_VIDMODE_QP: strcpy(mode, "QP"); break; - } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"width\":%d,\"height\":%d,\"fps\":%d,\"mode\":\"%s\",\"bitrate\":%d}", - app_config.mjpeg_width, app_config.mjpeg_height, app_config.mjpeg_fps, mode, - app_config.mjpeg_bitrate); - send_and_close(client_fd, response, respLen); - continue; + disable_mjpeg(); + if (app_config.mjpeg_enable) enable_mjpeg(); } - if (EQUALS(uri, "/api/mp4")) { - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "width")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mp4_width = result; - } else if (EQUALS(key, "height")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mp4_height = result; - } else if (EQUALS(key, "fps")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mp4_fps = result; - } else if (EQUALS(key, "bitrate")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mp4_bitrate = result; - } else if (EQUALS(key, "h265")) { - if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) - app_config.mp4_codecH265 = 1; - else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) - app_config.mp4_codecH265 = 0; - } else if (EQUALS(key, "mode")) { - if (EQUALS_CASE(value, "CBR")) - app_config.mp4_mode = HAL_VIDMODE_CBR; - else if (EQUALS_CASE(value, "VBR")) - app_config.mp4_mode = HAL_VIDMODE_VBR; - else if (EQUALS_CASE(value, "QP")) - app_config.mp4_mode = HAL_VIDMODE_QP; - else if (EQUALS_CASE(value, "ABR")) - app_config.mp4_mode = HAL_VIDMODE_ABR; - else if (EQUALS_CASE(value, "AVBR")) - app_config.mp4_mode = HAL_VIDMODE_AVBR; - } else if (EQUALS(key, "profile")) { - if (EQUALS_CASE(value, "BP") || EQUALS_CASE(value, "BASELINE")) - app_config.mp4_profile = HAL_VIDPROFILE_BASELINE; - else if (EQUALS_CASE(value, "MP") || EQUALS_CASE(value, "MAIN")) - app_config.mp4_profile = HAL_VIDPROFILE_MAIN; - else if (EQUALS_CASE(value, "HP") || EQUALS_CASE(value, "HIGH")) - app_config.mp4_profile = HAL_VIDPROFILE_HIGH; - } - } + char mode[5] = "\0"; + switch (app_config.mjpeg_mode) { + case HAL_VIDMODE_CBR: strcpy(mode, "CBR"); break; + case HAL_VIDMODE_VBR: strcpy(mode, "VBR"); break; + case HAL_VIDMODE_QP: strcpy(mode, "QP"); break; + } + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"width\":%d,\"height\":%d,\"fps\":%d,\"mode\":\"%s\",\"bitrate\":%d}", + app_config.mjpeg_width, app_config.mjpeg_height, app_config.mjpeg_fps, mode, + app_config.mjpeg_bitrate); + send_and_close(req->clntFd, response, respLen); + return; + } - disable_mp4(); - if (app_config.mp4_enable) enable_mp4(); + if (EQUALS(req->uri, "/api/mp4")) { + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "width")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mp4_width = result; + } else if (EQUALS(key, "height")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mp4_height = result; + } else if (EQUALS(key, "fps")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mp4_fps = result; + } else if (EQUALS(key, "bitrate")) { + short result = strtol(value, &remain, 10); + if (remain != value) + app_config.mp4_bitrate = result; + } else if (EQUALS(key, "h265")) { + if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) + app_config.mp4_codecH265 = 1; + else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) + app_config.mp4_codecH265 = 0; + } else if (EQUALS(key, "mode")) { + if (EQUALS_CASE(value, "CBR")) + app_config.mp4_mode = HAL_VIDMODE_CBR; + else if (EQUALS_CASE(value, "VBR")) + app_config.mp4_mode = HAL_VIDMODE_VBR; + else if (EQUALS_CASE(value, "QP")) + app_config.mp4_mode = HAL_VIDMODE_QP; + else if (EQUALS_CASE(value, "ABR")) + app_config.mp4_mode = HAL_VIDMODE_ABR; + else if (EQUALS_CASE(value, "AVBR")) + app_config.mp4_mode = HAL_VIDMODE_AVBR; + } else if (EQUALS(key, "profile")) { + if (EQUALS_CASE(value, "BP") || EQUALS_CASE(value, "BASELINE")) + app_config.mp4_profile = HAL_VIDPROFILE_BASELINE; + else if (EQUALS_CASE(value, "MP") || EQUALS_CASE(value, "MAIN")) + app_config.mp4_profile = HAL_VIDPROFILE_MAIN; + else if (EQUALS_CASE(value, "HP") || EQUALS_CASE(value, "HIGH")) + app_config.mp4_profile = HAL_VIDPROFILE_HIGH; + } } - char h265[6] = "false"; - char mode[5] = "\0"; - char profile[3] = "\0"; - if (app_config.mp4_codecH265) - strcpy(h265, "true"); - switch (app_config.mp4_mode) { - case HAL_VIDMODE_CBR: strcpy(mode, "CBR"); break; - case HAL_VIDMODE_VBR: strcpy(mode, "VBR"); break; - case HAL_VIDMODE_QP: strcpy(mode, "QP"); break; - case HAL_VIDMODE_ABR: strcpy(mode, "ABR"); break; - case HAL_VIDMODE_AVBR: strcpy(mode, "AVBR"); break; - } - switch (app_config.mp4_profile) { - case HAL_VIDPROFILE_BASELINE: strcpy(profile, "BP"); break; - case HAL_VIDPROFILE_MAIN: strcpy(profile, "MP"); break; - case HAL_VIDPROFILE_HIGH: strcpy(profile, "HP"); break; - } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"width\":%d,\"height\":%d,\"fps\":%d,\"h265\":%s,\"mode\":\"%s\",\"profile\":\"%s\",\"bitrate\":%d}", - app_config.mp4_width, app_config.mp4_height, app_config.mp4_fps, h265, mode, - profile, app_config.mp4_bitrate); - send_and_close(client_fd, response, respLen); - continue; + disable_mp4(); + if (app_config.mp4_enable) enable_mp4(); } - if (app_config.night_mode_enable && EQUALS(uri, "/api/night")) { - if (app_config.ir_sensor_pin == 999 && !EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "active")) { - if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) - set_night_mode(1); - else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) - set_night_mode(0); - } + char h265[6] = "false"; + char mode[5] = "\0"; + char profile[3] = "\0"; + if (app_config.mp4_codecH265) + strcpy(h265, "true"); + switch (app_config.mp4_mode) { + case HAL_VIDMODE_CBR: strcpy(mode, "CBR"); break; + case HAL_VIDMODE_VBR: strcpy(mode, "VBR"); break; + case HAL_VIDMODE_QP: strcpy(mode, "QP"); break; + case HAL_VIDMODE_ABR: strcpy(mode, "ABR"); break; + case HAL_VIDMODE_AVBR: strcpy(mode, "AVBR"); break; + } + switch (app_config.mp4_profile) { + case HAL_VIDPROFILE_BASELINE: strcpy(profile, "BP"); break; + case HAL_VIDPROFILE_MAIN: strcpy(profile, "MP"); break; + case HAL_VIDPROFILE_HIGH: strcpy(profile, "HP"); break; + } + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"width\":%d,\"height\":%d,\"fps\":%d,\"h265\":%s,\"mode\":\"%s\",\"profile\":\"%s\",\"bitrate\":%d}", + app_config.mp4_width, app_config.mp4_height, app_config.mp4_fps, h265, mode, + profile, app_config.mp4_bitrate); + send_and_close(req->clntFd, response, respLen); + return; + } + + if (app_config.night_mode_enable && EQUALS(req->uri, "/api/night")) { + if (app_config.ir_sensor_pin == 999 && !EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "active")) { + if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) + set_night_mode(1); + else if (EQUALS_CASE(value, "false") || EQUALS(value, "0")) + set_night_mode(0); } } - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"active\":%s}", - night_mode_is_enabled() ? "true" : "false"); - send_and_close(client_fd, response, respLen); - continue; } + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"active\":%s}", + night_mode_is_enabled() ? "true" : "false"); + send_and_close(req->clntFd, response, respLen); + return; + } - if (app_config.osd_enable && STARTS_WITH(uri, "/api/osd/")) { - char *remain; - int respLen; - short id = strtol(uri + 9, &remain, 10); - if (remain == uri + 9 || id < 0 || id >= MAX_OSD) { + if (app_config.osd_enable && STARTS_WITH(req->uri, "/api/osd/")) { + char *remain; + int respLen; + short id = strtol(req->uri + 9, &remain, 10); + if (remain == req->uri + 9 || id < 0 || id >= MAX_OSD) { + respLen = sprintf(response, + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + "The requested OSD ID was not found.\r\n" + ); + send_and_close(req->clntFd, response, respLen); + return; + } + if (EQUALS(req->method, "POST")) { + char *type = request_header("Content-Type"); + if (STARTS_WITH(type, "multipart/form-data")) { + char *bound = strstr(type, "boundary=") + strlen("boundary="); + + char *payloadb = strstr(req->payload, bound); + payloadb = memstr(payloadb, "\r\n\r\n", req->total - (payloadb - req->input), 4); + if (payloadb) payloadb += 4; + + char *payloade = memstr(payloadb, bound, + req->total - (payloadb - req->input), strlen(bound)); + if (payloade) payloade -= 4; + + char path[32]; + sprintf(path, "/tmp/osd%d.bmp", id); + FILE *img = fopen(path, "wb"); + fwrite(payloadb, sizeof(char), payloade - payloadb, img); + fclose(img); + + strcpy(osds[id].text, ""); + osds[id].updt = 1; + } else { respLen = sprintf(response, - "HTTP/1.1 404 Not Found\r\n" \ - "Content-Type: text/plain\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "The requested OSD ID was not found.\r\n" \ + "HTTP/1.1 415 Unsupported Media Type\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + "The payload must be presented as multipart/form-data.\r\n" ); - send_and_close(client_fd, response, respLen); - continue; + send_and_close(req->clntFd, response, respLen); + return; } - if (EQUALS(method, "POST")) { - char *type = request_header("Content-Type"); - if (STARTS_WITH(type, "multipart/form-data")) { - char *bound = strstr(type, "boundary=") + strlen("boundary="); - - char *payloadb = strstr(payload, bound); - payloadb = memstr(payloadb, "\r\n\r\n", total - (payloadb - request), 4); - if (payloadb) payloadb += 4; - - char *payloade = memstr(payloadb, bound, - total - (payloadb - request), strlen(bound)); - if (payloade) payloade -= 4; - - char path[32]; - sprintf(path, "/tmp/osd%d.bmp", id); - FILE *img = fopen(path, "wb"); - fwrite(payloadb, sizeof(char), payloade - payloadb, img); - fclose(img); - - strcpy(osds[id].text, ""); - osds[id].updt = 1; - } else { - respLen = sprintf(response, - "HTTP/1.1 415 Unsupported Media Type\r\n" \ - "Content-Type: text/plain\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "The payload must be presented as multipart/form-data.\r\n" \ - ); - send_and_close(client_fd, response, respLen); - continue; + } + if (!EMPTY(req->query)) + { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "font")) + strcpy(osds[id].font, !EMPTY(value) ? value : DEF_FONT); + else if (EQUALS(key, "text")) + strcpy(osds[id].text, value); + else if (EQUALS(key, "size")) { + double result = strtod(value, &remain); + if (remain == value) continue; + osds[id].size = (result != 0 ? result : DEF_SIZE); } - } - if (!EMPTY(query)) - { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "font")) - strcpy(osds[id].font, !EMPTY(value) ? value : DEF_FONT); - else if (EQUALS(key, "text")) - strcpy(osds[id].text, value); - else if (EQUALS(key, "size")) { - double result = strtod(value, &remain); - if (remain == value) continue; - osds[id].size = (result != 0 ? result : DEF_SIZE); - } - else if (EQUALS(key, "color")) { - char base = 16; - if (strlen(value) > 1 && value[1] == 'x') base = 0; - short result = strtol(value, &remain, base); - if (remain != value) - osds[id].color = result; - } - else if (EQUALS(key, "opal")) { - short result = strtol(value, &remain, 10); - if (remain != value) - osds[id].opal = result & 0xFF; - } - else if (EQUALS(key, "posx")) { - short result = strtol(value, &remain, 10); - if (remain != value) - osds[id].posx = result; - } - else if (EQUALS(key, "posy")) { - short result = strtol(value, &remain, 10); - if (remain != value) - osds[id].posy = result; - } + else if (EQUALS(key, "color")) { + char base = 16; + if (strlen(value) > 1 && value[1] == 'x') base = 0; + short result = strtol(value, &remain, base); + if (remain != value) + osds[id].color = result; + } + else if (EQUALS(key, "opal")) { + short result = strtol(value, &remain, 10); + if (remain != value) + osds[id].opal = result & 0xFF; + } + else if (EQUALS(key, "posx")) { + short result = strtol(value, &remain, 10); + if (remain != value) + osds[id].posx = result; + } + else if (EQUALS(key, "posy")) { + short result = strtol(value, &remain, 10); + if (remain != value) + osds[id].posy = result; } - osds[id].updt = 1; } - respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"id\":%d,\"color\":%#x,\"opal\":%d\"pos\":[%d,%d],\"font\":\"%s\",\"size\":%.1f,\"text\":\"%s\"}", - id, osds[id].color, osds[id].opal, osds[id].posx, osds[id].posy, osds[id].font, osds[id].size, osds[id].text); - send_and_close(client_fd, response, respLen); - continue; + osds[id].updt = 1; } + respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"id\":%d,\"color\":%#x,\"opal\":%d\"pos\":[%d,%d],\"font\":\"%s\",\"size\":%.1f,\"text\":\"%s\"}", + id, osds[id].color, osds[id].opal, osds[id].posx, osds[id].posy, osds[id].font, osds[id].size, osds[id].text); + send_and_close(req->clntFd, response, respLen); + return; + } - if (EQUALS(uri, "/api/status")) { - struct sysinfo si; - sysinfo(&si); - char memory[16], uptime[48]; - short free = (si.freeram + si.bufferram) / 1024 / 1024; - short total = si.totalram / 1024 / 1024; - sprintf(memory, "%d/%dMB", total - free, total); - if (si.uptime > 86400) - sprintf(uptime, "%ld days, %ld:%02ld:%02ld", si.uptime / 86400, (si.uptime % 86400) / 3600, (si.uptime % 3600) / 60, si.uptime % 60); - else if (si.uptime > 3600) - sprintf(uptime, "%ld:%02ld:%02ld", si.uptime / 3600, (si.uptime % 3600) / 60, si.uptime % 60); - else - sprintf(uptime, "%ld:%02ld", si.uptime / 60, si.uptime % 60); - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"chip\":\"%s\",\"loadavg\":[%.2f,%.2f,%.2f],\"memory\":\"%s\",\"uptime\":\"%s\"}", - chip, si.loads[0] / 65536.0, si.loads[1] / 65536.0, si.loads[2] / 65536.0, - memory, uptime); - send_and_close(client_fd, response, respLen); - continue; - } + if (EQUALS(req->uri, "/api/status")) { + struct sysinfo si; + sysinfo(&si); + char memory[16], uptime[48]; + short free = (si.freeram + si.bufferram) / 1024 / 1024; + short total = si.totalram / 1024 / 1024; + sprintf(memory, "%d/%dMB", total - free, total); + if (si.uptime > 86400) + sprintf(uptime, "%ld days, %ld:%02ld:%02ld", si.uptime / 86400, (si.uptime % 86400) / 3600, (si.uptime % 3600) / 60, si.uptime % 60); + else if (si.uptime > 3600) + sprintf(uptime, "%ld:%02ld:%02ld", si.uptime / 3600, (si.uptime % 3600) / 60, si.uptime % 60); + else + sprintf(uptime, "%ld:%02ld", si.uptime / 60, si.uptime % 60); + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"chip\":\"%s\",\"loadavg\":[%.2f,%.2f,%.2f],\"memory\":\"%s\",\"uptime\":\"%s\"}", + chip, si.loads[0] / 65536.0, si.loads[1] / 65536.0, si.loads[2] / 65536.0, + memory, uptime); + send_and_close(req->clntFd, response, respLen); + return; + } - if (EQUALS(uri, "/api/time")) { - struct timespec t; - if (!EMPTY(query)) { - char *remain; - while (query) { - char *value = split(&query, "&"); - if (!value || !*value) continue; - unescape_uri(value); - char *key = split(&value, "="); - if (!key || !*key || !value || !*value) continue; - if (EQUALS(key, "fmt")) { - strncpy(timefmt, value, 32); - } else if (EQUALS(key, "ts")) { - short result = strtol(value, &remain, 10); - if (remain == value) continue; - t.tv_sec = result; - clock_settime(0, &t); - } + if (EQUALS(req->uri, "/api/time")) { + struct timespec t; + if (!EMPTY(req->query)) { + char *remain; + while (req->query) { + char *value = split(&req->query, "&"); + if (!value || !*value) continue; + unescape_uri(value); + char *key = split(&value, "="); + if (!key || !*key || !value || !*value) continue; + if (EQUALS(key, "fmt")) { + strncpy(timefmt, value, 32); + } else if (EQUALS(key, "ts")) { + short result = strtol(value, &remain, 10); + if (remain == value) continue; + t.tv_sec = result; + clock_settime(0, &t); } } - clock_gettime(0, &t); - int respLen = sprintf(response, - "HTTP/1.1 200 OK\r\n" \ - "Content-Type: application/json;charset=UTF-8\r\n" \ - "Connection: close\r\n" \ - "\r\n" \ - "{\"fmt\":\"%s\",\"ts\":%d}", timefmt, t.tv_sec); - send_and_close(client_fd, response, respLen); - continue; } + clock_gettime(0, &t); + int respLen = sprintf(response, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "{\"fmt\":\"%s\",\"ts\":%d}", timefmt, t.tv_sec); + send_and_close(req->clntFd, response, respLen); + return; + } - if (app_config.web_enable_static && send_file(client_fd, uri)) - continue; + if (app_config.web_enable_static && send_file(req->clntFd, req->uri)) + return; - send_and_close(client_fd, (char*)error400, strlen(error400)); + send_and_close(req->clntFd, (char*)error400, strlen(error400)); +} + +void *server_thread(void *vargp) { + struct Request req = {0}; + int ret, server_fd = *((int *)vargp); + int enable = 1; + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + HAL_WARNING("server", "setsockopt(SO_REUSEADDR) failed"); + fflush(stdout); + } + struct sockaddr_in client, server = { + .sin_family = AF_INET, + .sin_port = htons(app_config.web_port), + .sin_addr.s_addr = htonl(INADDR_ANY) + }; + if (ret = bind(server_fd, (struct sockaddr *)&server, sizeof(server))) { + HAL_DANGER("server", "%s (%d)\n", strerror(errno), errno); + keepRunning = 0; + close_socket_fd(server_fd); + return NULL; + } + listen(server_fd, 128); + + req.input = malloc(REQSIZE); + + while (keepRunning) { + // Waiting for a new connection + if ((req.clntFd = accept(server_fd, NULL, NULL)) == -1) + break; + + parse_request(&req); + + respond_request(&req); } - if (request) - free(request); + if (req.input) + free(req.input); close_socket_fd(server_fd); HAL_INFO("server", "Thread has exited\n"); return NULL; } -int server_fd = -1; -pthread_t server_thread_id; - int start_server() { for (uint32_t i = 0; i < MAX_CLIENTS; ++i) { client_fds[i].socket_fd = -1;