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");