diff --git a/samples/http-server/README.md b/samples/http-server/README.md index ecee787..52fd953 100644 --- a/samples/http-server/README.md +++ b/samples/http-server/README.md @@ -1,6 +1,6 @@ ## http-server -This is an extremely simple static http server built with the library. For demonstration purposes, it only handles GET requests. Supported command line options: +This is an extremely simple static http server built with the library. Supported command line options: - `-m`: Maximum number of concurrent clients (default: 100) - `-p`: Port number to listen on (default: 8080) diff --git a/samples/http-server/main.c b/samples/http-server/main.c index e80c4c6..1b29d0f 100644 --- a/samples/http-server/main.c +++ b/samples/http-server/main.c @@ -15,16 +15,23 @@ #include "pool_day.h" #include "task.h" -#define MAX_VERB_SIZE 7 -#define MAX_BUFFER_SIZE 4096 +#define MAX_METHOD_SIZE 7 +#define MAX_BUFFER_SIZE 10 * 1024 #define DEFAULT_ROOT_DIR "./www" #define DEFAULT_PORT 8080 #define DEFAULT_MAX_CLIENTS 100 -#define STR(x) #x -#define MAKE_ERROR_BODY(status_code, msg) \ - "

" STR(status_code) " " #msg "

" +#define CONTENT_TYPE_TEXT_HTML "text/html" +#define CONTENT_TYPE_IMAGE_PNG "image/png" +#define CONTENT_TYPE_IMAGE_JPEG "image/jpeg" +#define CONTENT_TYPE_IMAGE_GIF "image/gif" +#define CONTENT_TYPE_TEXT_CSS "text/css" +#define CONTENT_TYPE_APP_JS "application/javascript" +#define CONTENT_TYPE_APP_OCTET "application/octet-stream" + +#define MAKE_ERROR_BODY(code, msg) \ + "

" #code " " #msg "

" typedef struct { uint32_t max_clients; @@ -33,7 +40,7 @@ typedef struct { } server_cfg_t; typedef struct { - char verb[MAX_VERB_SIZE]; + char method[MAX_METHOD_SIZE]; char resource[PATH_MAX]; } request_t; @@ -49,123 +56,250 @@ static void sig_handler(int signum) { static void on_client_connected(uint32_t tid, const void *param) { const client_t *cli = (client_t *)param; - printf("[+] task[%u]: client connected, ip=%s\n", tid, inet_ntoa(cli->addr)); + fprintf(stdout, "[+] task[%u]: client connected, ip=%s\n", tid, + inet_ntoa(cli->addr)); } static void on_client_disconnected(uint32_t tid, const void *param, void *ret_val) { const client_t *cli = (client_t *)param; + int ret = ret_val ? *((uint16_t *)ret_val) : -1; + + fprintf(stdout, "[+] task[%u]: client disconnected ip=%s, result=%u\n", tid, + inet_ntoa(cli->addr), ret); - printf("[+] task[%u]: client disconnected ip=%s, result=%u\n", tid, - inet_ntoa(cli->addr), *((uint16_t *)ret_val)); - free(ret_val); + if (ret_val) { + free(ret_val); + } } -static void parse_request(const char *buffer, - request_t *req) { - // simple parsing logic for demonstration purposes - sscanf(buffer, "%s %s", req->verb, req->resource); +static const char *get_content_type(const char *path) { + const char *ext = strrchr(path, '.'); + + if (!ext) { + return CONTENT_TYPE_APP_OCTET; + } + + if (!strcmp(ext, ".html") || !strcmp(ext, ".htm")) { + return CONTENT_TYPE_TEXT_HTML; + } + + if (!strcmp(ext, ".png")) { + return CONTENT_TYPE_IMAGE_PNG; + } + + if (!strcmp(ext, ".jpg") || !strcmp(ext, ".jpeg")) { + return CONTENT_TYPE_IMAGE_JPEG; + } + + if (!strcmp(ext, ".gif")) { + return CONTENT_TYPE_IMAGE_GIF; + } + + if (!strcmp(ext, ".css")) { + return CONTENT_TYPE_TEXT_CSS; + } + + if (!strcmp(ext, ".js")) { + return CONTENT_TYPE_APP_JS; + } + + return CONTENT_TYPE_APP_OCTET; +} + +static void parse_request(const char *buffer, request_t *req) { + sscanf(buffer, "%s %s", req->method, req->resource); } -static void assemble_reply(char *buffer, size_t buffer_size, int status_code, - const char *status_str, const char *body) { - const char *reply_fmt = +static char *build_http_header(int status_code, const char *status_str, + const char *content_type, size_t content_length, + size_t *header_len) { + + const char *fmt = "HTTP/1.1 %d %s\r\n" - "Content-Type: text/html\r\n" - "\r\n%s"; + "Content-Type: %s\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n"; + + *header_len = snprintf(NULL, 0, fmt, status_code, status_str, content_type, + content_length); + + char *header = malloc(*header_len + 1); + if (!header) { + return NULL; + } - snprintf(buffer, buffer_size, reply_fmt, status_code, status_str, body); + snprintf(header, *header_len + 1, fmt, status_code, status_str, content_type, + content_length); + + return header; +} + +static int assemble_reply(char **buffer, const char *header, size_t header_len, + const char *body, size_t body_len) { + size_t total = header_len + body_len; + + *buffer = malloc(total); + if (!(*buffer)) { + return 0; + } + + memcpy(*buffer, header, header_len); + memcpy(*buffer + header_len, body, body_len); + + return total; } -static char *get_resource(const char *res_name) { +static char *get_resource(const char *path, size_t *len) { struct stat st; - FILE *file = fopen(res_name, "r"); + FILE *file = fopen(path, "rb"); if (!file) { - printf("[-] fail to open the requested resource: %s\n", strerror(errno)); return NULL; } - stat(res_name, &st); - - char *content = calloc(1, st.st_size + 1); - if (!content) { - printf( - "[-] no memory available to put the requested resource's content on\n"); + if (stat(path, &st) < 0) { fclose(file); return NULL; } - if (!fread(content, 1, st.st_size, file)) { - printf("[-] fail to read the requested resource\n"); - free(content); + char *data = malloc(st.st_size); + if (!data) { fclose(file); return NULL; } + size_t read_bytes = fread(data, 1, st.st_size, file); fclose(file); - return content; + if (read_bytes != (size_t)st.st_size) { + free(data); + return NULL; + } + + *len = read_bytes; + return data; } -static int handle_get_request(char *reply_buffer, size_t buffer_size, +static int handle_get_request(char **reply_buffer, size_t *reply_buffer_size, const char *resource) { - char *res = get_resource(resource); - int status_code; + size_t header_len; + + if (strstr(resource, "..")) { + return 400; + } + + size_t res_len; + char *res = get_resource(resource, &res_len); if (res) { - status_code = 200; - assemble_reply(reply_buffer, buffer_size, status_code, "OK", res); + const char *content_type = get_content_type(resource); + char *header = build_http_header(200, "OK", content_type, res_len, + &header_len); + + *reply_buffer_size = assemble_reply(reply_buffer, header, header_len, res, + res_len); + + free(header); free(res); - } else { - status_code = 404; - assemble_reply(reply_buffer, buffer_size, status_code, "Not Found", - MAKE_ERROR_BODY(404, Not Found)); + + return 200; } - return status_code; + const char *body = MAKE_ERROR_BODY(404, Not Found); + size_t body_len = strlen(body); + char *header = build_http_header(404, "Not Found", CONTENT_TYPE_TEXT_HTML, + body_len, &header_len); + + *reply_buffer_size = assemble_reply(reply_buffer, header, header_len, body, + body_len); + + free(header); + + return 404; } -static void *handle_client(void *param) { - char buffer[MAX_BUFFER_SIZE] = {0}; - client_t *cli = (client_t *)param; - int *ret = calloc(1, sizeof(int)); +static int handle_request(const char *req_buffer, + char **reply_buffer, + size_t *reply_buffer_len) { + request_t req; memset(&req, 0, sizeof(request_t)); - ssize_t received = recv(cli->fd, buffer, sizeof(buffer) - 1, 0); - if (received > 0) { - parse_request(buffer, &req); + parse_request(req_buffer, &req); + if (strcmp(req.method, "GET") != 0) { + return 501; + } - printf("[+] received request: %s %s\n", req.verb, req.resource); + fprintf(stdout, "[+] %s %s\n", req.method, req.resource); - memset(buffer, 0, sizeof(buffer)); - if (!strcmp(req.verb, "GET")) { - *ret = handle_get_request(buffer, MAX_BUFFER_SIZE, &req.resource[1]); + const char *resource = req.resource[0] == '/' ? + req.resource + 1 : + req.resource; - send(cli->fd, buffer, strlen(buffer), 0); + return handle_get_request(reply_buffer, reply_buffer_len, resource); +} + +static void send_all(int fd, const char *buffer, size_t len) { + size_t total = 0; + + while (total < len) { + ssize_t sent = send(fd, buffer + total, len - total, 0); + + if (sent <= 0) { + break; + } + + total += sent; + } +} + +static void *handle_new_connection(void *param) { + client_t *cli = (client_t *)param; + int *ret = NULL; + + char *req_buffer = calloc(1, MAX_BUFFER_SIZE); + if (!req_buffer) { + close(cli->fd); + return NULL; + } + + ssize_t received = recv(cli->fd, req_buffer, MAX_BUFFER_SIZE - 1, 0); + + if (received > 0) { + char *reply_buffer = NULL; + size_t reply_len = 0; + + ret = calloc(1, sizeof(int)); + *ret = handle_request(req_buffer, &reply_buffer, &reply_len); + + if (reply_buffer && reply_len > 0) { + send_all(cli->fd, reply_buffer, reply_len); + free(reply_buffer); } } + free(req_buffer); close(cli->fd); return (void *)ret; } -static void parse_args(int argc, char **argv, server_cfg_t *cfg) { +static void fill_cfg(int argc, char **argv, server_cfg_t *cfg) { int opt; while ((opt = getopt(argc, argv, "m:p:r:")) != -1) { switch (opt) { case 'm': - cfg->max_clients = (uint32_t)atoi(optarg); + cfg->max_clients = atoi(optarg); break; case 'p': - cfg->port = (uint16_t)atoi(optarg); + cfg->port = atoi(optarg); break; case 'r': - memcpy(cfg->root_dir, optarg, strlen(optarg) + 1); + strcpy(cfg->root_dir, optarg); break; } } @@ -178,8 +312,8 @@ static void parse_args(int argc, char **argv, server_cfg_t *cfg) { cfg->port = DEFAULT_PORT; } - if (cfg->root_dir[0] == '\0') { - memcpy(cfg->root_dir, DEFAULT_ROOT_DIR, strlen(DEFAULT_ROOT_DIR) + 1); + if (!cfg->root_dir[0]) { + strcpy(cfg->root_dir, DEFAULT_ROOT_DIR); } } @@ -188,7 +322,6 @@ static int setup_socket(int *sock_fd, const server_cfg_t *cfg) { *sock_fd = socket(AF_INET, SOCK_STREAM, 0); if (*sock_fd < 0) { - printf("[-] fail to create the server socket: %s\n", strerror(errno)); return 1; } @@ -199,13 +332,11 @@ static int setup_socket(int *sock_fd, const server_cfg_t *cfg) { addr.sin_port = htons(cfg->port); if (bind(*sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - printf("[-] fail to create the server socket: %s\n", strerror(errno)); close(*sock_fd); return 1; } if (listen(*sock_fd, cfg->max_clients) < 0) { - printf("[-] fail to create the server socket: %s\n", strerror(errno)); close(*sock_fd); return 1; } @@ -213,52 +344,41 @@ static int setup_socket(int *sock_fd, const server_cfg_t *cfg) { return 0; } -static int run_server(const server_cfg_t *cfg) { - int ret, server_fd; - pool_day_t pool; - struct sockaddr_in cli_addr; - socklen_t cli_len = sizeof(cli_addr); - fd_set set; - - if (setup_socket(&server_fd, cfg) != 0) { - printf("[-] failed to setup server socket\n"); +static int setup_server(int *sock_fd, const server_cfg_t *cfg, + pool_day_t *pool) { + if (setup_socket(sock_fd, cfg) != 0) { return 1; } - if (!(pool = create_pool(cfg->max_clients))) { - printf("[-] fail to setup the server pool\n"); - close(server_fd); + if (!(*pool = create_pool(cfg->max_clients))) { + close(*sock_fd); return 1; } - printf("[+] starting server with max_clients=%u, port=%u, root_dir=%s\n", - cfg->max_clients, cfg->port, cfg->root_dir); + return 0; +} - if (chdir(cfg->root_dir)) { - printf("[-] fail to run the server: %s\n", strerror(errno)); - return 1; - } +static int server_mainloop(int server_fd, pool_day_t pool) { + fd_set set; while (1) { FD_ZERO(&set); FD_SET(server_fd, &set); - ret = select(server_fd + 1, &set, NULL, NULL, NULL); - if ((ret == -1) && (errno == EINTR)) { - printf("[-] exiting server...\n"); + if (select(server_fd + 1, &set, NULL, NULL, NULL) < 0) { break; } if (FD_ISSET(server_fd, &set)) { - int client_fd = accept(server_fd, (struct sockaddr *)&cli_addr, &cli_len); + struct sockaddr_in cli_addr; + socklen_t len = sizeof(cli_addr); - if (client_fd == -1) { - printf("[-] failed to accept the incoming client: %s\n", - strerror(errno)); + int client_fd = accept(server_fd, (struct sockaddr *)&cli_addr, &len); + if (client_fd < 0) { continue; } - task_t task = create_async_task(client_fd, handle_client, + task_t task = create_async_task(client_fd, handle_new_connection, (void *)&((client_t) { .fd = (uint32_t)client_fd, .addr = cli_addr.sin_addr }), @@ -266,11 +386,7 @@ static int run_server(const server_cfg_t *cfg) { on_client_connected, on_client_disconnected); - if (enqueue_task(pool, task) != POOL_DAY_SUCCESS) { - printf("[-] failed to enqueue the request task\n"); - destroy_task(task); - close(client_fd); - } + enqueue_task(pool, task); } } @@ -280,14 +396,35 @@ static int run_server(const server_cfg_t *cfg) { return 0; } +static int run_server(const server_cfg_t *cfg) { + int server_fd; + pool_day_t pool; + + if (setup_server(&server_fd, cfg, &pool)) { + fprintf(stderr, "[-] fail to setup the server\n"); + return 1; + } + + if (chdir(cfg->root_dir)) { + fprintf(stderr, "[-] fail to run the server on '%s': %s\n", cfg->root_dir, + strerror(errno)); + return 1; + } + + fprintf(stdout, + "[+] starting server with max_clients=%u, port=%u, root_dir=%s\n", + cfg->max_clients, cfg->port, cfg->root_dir); + + return server_mainloop(server_fd, pool); +} + int main(int argc, char **argv) { server_cfg_t cfg; - memset(&cfg, 0, sizeof(server_cfg_t)); - signal(SIGINT, sig_handler); - signal(SIGTERM, sig_handler); - parse_args(argc, argv, &cfg); + memset(&cfg, 0, sizeof(server_cfg_t)); + fill_cfg(argc, argv, &cfg); + return run_server(&cfg); } diff --git a/src/pool_day.c b/src/pool_day.c index ddb3028..b547246 100644 --- a/src/pool_day.c +++ b/src/pool_day.c @@ -81,9 +81,8 @@ pool_day_retcode_t enqueue_task(pool_day_t pool, task_t task) { return POOL_DAY_ERROR_NULL_PARAM; } - POOL_DAY_DEBUG("task ID: %u", task->id); - POOL_DAY_DEBUG("task callback: %p", task->task); - POOL_DAY_DEBUG("task parameter: %p", task->param); + POOL_DAY_DEBUG("task id=%u, callback=%p, parameter=%p", task->id, task->task, + task->param); task->ret_val = NULL; task->is_orphan = false; @@ -100,7 +99,7 @@ pool_day_retcode_t enqueue_task(pool_day_t pool, task_t task) { pool_day_t create_pool(uint32_t pool_size) { pool_day_t pool; - POOL_DAY_DEBUG("pool size: %u", pool_size); + POOL_DAY_DEBUG("pool_size=%u", pool_size); if (!pool_size) { POOL_DAY_ERROR("bad pool size");