diff options
Diffstat (limited to 'src/http.c')
| -rw-r--r-- | src/http.c | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..db497d5 --- /dev/null +++ b/src/http.c @@ -0,0 +1,586 @@ +#include <chttp/http.h> + +#define MAX_DATA_SIZE 256 +#define BUFFER_SIZE 4096 +#define REQUEST_ARENA_SIZE 1024 + +int volatile keepRunning = 1; + +struct { + http_response_code code; + const_string string; +} response_strings [] = { + {100, CS_STATIC("Continue")}, + {101, CS_STATIC("Switching Protocols")}, + {102, CS_STATIC("Processing")}, + {103, CS_STATIC("Early Hints")}, + {200, CS_STATIC("OK")}, + {201, CS_STATIC("Created")}, + {202, CS_STATIC("Accepted")}, + {204, CS_STATIC("No Content")}, + {300, CS_STATIC("Multiple Choices")}, + {301, CS_STATIC("Moved Permanently")}, + {302, CS_STATIC("Found")}, + {303, CS_STATIC("See Other")}, + {304, CS_STATIC("Not Modified")}, + {307, CS_STATIC("Temporary Redirect")}, + {308, CS_STATIC("Permanent Redirect")}, + {400, CS_STATIC("Bad Request")}, + {401, CS_STATIC("Unauthorized")}, + {402, CS_STATIC("Payment Required")}, + {403, CS_STATIC("Forbidden")}, + {404, CS_STATIC("Not Found")}, + {405, CS_STATIC("Method Not Allowed")}, + {406, CS_STATIC("Not Acceptable")}, + {408, CS_STATIC("Request Timeout")}, + {418, CS_STATIC("Im A Teepot")}, + {500, CS_STATIC("Internal Server Error")}, + {501, CS_STATIC("Not Implemented")}, + {502, CS_STATIC("Bad Gateway")}, + {503, CS_STATIC("Service Unavailable")}, + {505, CS_STATIC("Http Version Not Suppported")}, +}; + +const_string get_response_string(http_response_code code) { + for (int i = 0; sizeof(response_strings)/sizeof(response_strings[0]) ;i++) { + if (response_strings[i].code == code) { + return response_strings[i].string; + } + } + return CS(""); +} + + +void sigchld_handler() { + // waitpid() might overwrite errno, so we save and restore it: + int saved_errno = errno; + + while(waitpid(-1, NULL, WNOHANG) > 0); + + errno = saved_errno; +} + +void http_log(http_log_level log, char *fmt, ...) { + switch (log) { + case HTTP_INFO: + fprintf(stderr, "[INFO] "); + break; + + case HTTP_WARNING: + fprintf(stderr, "[WARNING] "); + break; + + case HTTP_ERROR: + fprintf(stderr, "[ERROR] "); + break; + } + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + + +void *get_in_addr(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +int get_in_port(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return ntohs((((struct sockaddr_in*)sa)->sin_port)); + } + + return (((struct sockaddr_in6*)sa)->sin6_port); +} + +void set_context_value(arena *arena, http_context *ctx, http_context_value kv) { + for (size_t i = 0; i < ctx->len; i++) { + http_context_value *ctx_val = (ctx->data + i); + if (cs_eq(kv.key, ctx_val->key)) { + ctx_val->val = kv.val; + return; + } + } + arena_da_append(arena, ctx, kv); +} + +void *get_context_value(http_context *ctx, char* key) { + if (ctx == NULL) { + return NULL; + } + + const_string key_cs = cs_from_cstr(key); + for (size_t i = 0; i < ctx->len; i++) { + http_context_value *ctx_val = (ctx->data + i); + if (cs_eq(key_cs, ctx_val->key)) { + return ctx_val->val; + } + } + return NULL; +} + +void http_internal_server_error_handler(http_request req) { + const_string *error = get_context_value(req.ctx, "error"); + req.resp->code = INTERNAL_SERVER_ERROR; + req.resp->body = *error; + http_send(req); +} + +void http_not_found_handler(http_request req) { + req.resp->code = NOT_FOUND; + http_send(req); +} + +void http_run_next(http_middleware *self, http_request req) { + if (self->next == NULL) { + (self->handler)(req); + return; + } + + (self->next->func)(self->next, req); +}; + +void http_register_handler_middleware(arena *arena, http_handler *hand, http_middleware_func func) { + http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); + middleware->func = func; + middleware->handler = hand->func; + if (hand->middleware == NULL) { + hand->middleware = middleware; + return; + } + + http_middleware **prev = &hand->middleware; + http_middleware *next = hand->middleware; + while (next->next != NULL) { + prev = &next; + next = next->next; + } + middleware->next = next; + *prev = middleware; +} + +void http_register_global_middleware(http_server *serv, http_middleware_func func) { + arena *arena = serv->arena; + + http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); + http_handler_func *handler = arena_alloc(arena, sizeof(http_handler_func)); + middleware->func = func; + middleware->handler = *handler; + if (serv->global_middleware == NULL) { + http_global_middleware *global_middleware = arena_alloc(arena, sizeof(http_global_middleware)); + global_middleware->start = middleware; + global_middleware->end = middleware; + serv->global_middleware = global_middleware; + return; + } + + serv->global_middleware->end->next = middleware; + serv->global_middleware->end = middleware; +} + +int init_server(arena *arena, http_server *serv, char *addr, char *port) { + // serv initialization + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + int status = getaddrinfo(addr, port, &hints, &res); + if (status != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); + return 1; + } + + int sockfd = socket(res->ai_family, res->ai_socktype, 0); + if (sockfd < 0) { + perror("socket"); + return 1; + } + + int yes = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { + perror("setsockopt"); + return 1; + } + + if (bind(sockfd, res->ai_addr, res->ai_addrlen)) { + perror("bind"); + return 1; + } + + serv->arena = arena; + serv->addr = (struct sockaddr *)res->ai_addr; + serv->sockfd = sockfd; + + freeaddrinfo(res); + + struct sigaction sa; + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + return 1; + } + + return 0; +} + +http_handler *http_handle_path(http_server *serv, char* method, char* path, http_handler_func handler) { + http_handler hand = { + .method = cs_from_cstr(method), + .path = cs_from_cstr(path), + .func = handler, + }; + + arena_da_append(serv->arena, &serv->handlers, hand); + return &serv->handlers.data[serv->handlers.len-1]; +} + +const_string http_compose_response(arena *arena, http_response *resp) { + const_string_da header_da = {0}; + arena_da_append(arena, &header_da, CS("HTTP/1.1")); + + if (resp->code != 0) { + char buf[4]; + sprintf(buf, "%ld", resp->code); + arena_da_append(arena, &header_da, cs_from_cstr(buf)); + arena_da_append(arena, &header_da, get_response_string(resp->code)); + } else { + printf("Response is handeled, but no code is given\n"); + arena_da_append(arena, &header_da, CS("500")); + arena_da_append(arena, &header_da, get_response_string(INTERNAL_SERVER_ERROR)); + } + + const_string_da resp_da= {0}; + + const_string http_header = arena_cs_concat(arena, header_da, CS(" ")); + arena_da_append(arena, &resp_da, http_header); + + for (size_t i = 0; i < resp->headers.len; i++) { + KV *header = (resp->headers.data + i); + const_string header_str = {0}; + header_str = arena_cs_append(arena, header_str, header->key); + header_str = arena_cs_append(arena, header_str, CS(": ")); + header_str = arena_cs_append(arena, header_str, header->value); + arena_da_append(arena, &resp_da, header_str); + } + + arena_da_append(arena, &resp_da, CS("")); + if (resp->body.len != 0) { + arena_da_append(arena, &resp_da, resp->body); + } + + const_string str = arena_cs_concat(arena, resp_da, CS("\r\n")); + + return str; +} + + +void http_send_response(size_t inc_fd, const_string resp) { + const char *resp_cstr = resp.data; + int resp_len = resp.len; + + int sent = send(inc_fd, resp_cstr, resp_len, 0); + while (sent > 0 && resp_len - sent > 0) { + resp_len -= sent; + sent = send(inc_fd, resp_cstr, resp_len, 0); + } + + if (sent < 0) { + perror("send"); + } +} + +void http_send(http_request req) { + arena *arena = req.arena; + + assert(req.resp->code); + const_string response = http_compose_response(arena, req.resp); + http_send_response(req.inc_fd, response); + req.resp->sent = true; +} + +void http_send_json(http_request req, const_string json) { + arena *arena = req.arena; + + int num_str_len = snprintf(NULL, 0, "%d", json.len); + char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", json.len)); + sprintf(len_str, "%d", json.len); + const_string len_cs = {len_str, num_str_len}; + + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/json")})); + req.resp->body = json; + + http_send(req); +} +void http_send_body(http_request req, const_string body) { + arena *arena = req.arena; + + int num_str_len = snprintf(NULL, 0, "%d", body.len+2); + char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", body.len)); + sprintf(len_str, "%d", body.len); + const_string len_cs = {len_str, num_str_len}; + + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/text")})); + req.resp->body = body; + + http_send(req); +} + +int chop_query(arena *arena, const_string *path, http_query_da *queries) { + const_string queries_str = *path; + int query_count = 0; + + if (!cs_try_chop_delim(&queries_str, '?', path)) { + return 0; + } + + const_string query_str; + + if (!cs_try_chop_delim(&queries_str, '&', &query_str)) { + query_str = queries_str; + } + + do { + KV query = {0}; + const_string key; + if (!cs_try_chop_delim(&query_str, '=', &key)) { + return -1; + } + + query.key = key; + query.value = query_str; + arena_da_append(arena, queries, query); + query_count++; + } while (cs_try_chop_delim(&queries_str, '&', &query_str)); + + return query_count; +} + +const_string chop_request(const_string *req_str, char delim, bool *parse_err) { + if (*parse_err) { + return CS(""); + } + const_string str; + if (!cs_try_chop_delim(req_str, delim, &str)) { + *parse_err = true; + return CS(""); + } + return str; +} + +int parse_request(arena *arena, size_t inc_fd, http_request *req) { + char buf[MAX_DATA_SIZE]; + const_string req_str = {0}; + + int got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); + if (got < 0) { + perror("recv"); + return REQUEST_TIMEOUT; + } + + buf[got] = '\0'; + + req_str = arena_cs_append(arena, req_str, cs_from_cstr(buf)); + + while (got == MAX_DATA_SIZE - 1) { + got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); + if (got < 0) { + perror("recv"); + return REQUEST_TIMEOUT; + } + + buf[got] = '\0'; + + const_string tmp = cs_from_parts(buf, got); + req_str = arena_cs_append(arena, req_str, tmp); + } + + bool parse_err = false; + + const_string method = chop_request(&req_str, ' ', &parse_err); + const_string path = chop_request(&req_str, ' ', &parse_err); + chop_request(&req_str, '\r', &parse_err); + req_str.data += 1; + + header_da headers = {0}; + while (!parse_err && req_str.data[0] != '\r') { + const_string header_str = chop_request(&req_str, '\r', &parse_err); + req_str.data += 1; + + KV header; + header.key = chop_request(&header_str, ':', &parse_err); + header_str.data++; + header.value = header_str; + + arena_da_append(arena, &headers, header); + } + chop_request(&req_str, '\n', &parse_err); + + if (parse_err) { + return BAD_REQUEST; + } + + http_query_da queries = {0}; + + if (chop_query(arena, &path, &queries) < 0) { + return BAD_REQUEST; + } + + + req->method = method; + req->path = path; + req->query = queries; + req->headers = headers; + req->body = req_str; + + return 0; +} + +bool check_handler(http_handler hand, http_request req) { + arena *arena = req.arena; + http_context *ctx = req.ctx; + + const_string handler_path = hand.path; + const_string req_path = req.path; + if (!(cs_eq(hand.method, req.method) || + (cs_eq(hand.method, CS("GET")) && cs_eq(req.method, CS("HEAD"))))) { + return false; + } + + const_string path; + bool entered = false; + + while (cs_try_chop_delim(&handler_path, ':', &path)) { + entered = true; + + const_string req_byte = {req_path.data, path.len}; + if (!cs_eq(path, req_byte)) { + return false; + } + + req_path.data += path.len; + req_path.len -= path.len; + + const_string name = cs_chop_delim(&handler_path, '/'); + const_string tmp = cs_chop_delim(&req_path, '/'); + const_string *value = arena_alloc(arena, sizeof(const_string)); + *value = tmp; + set_context_value(arena, ctx, (http_context_value){name, (void *)value}); + } + + if (!entered && !cs_eq(handler_path, req_path)) { + return false; + } + + return true; +} + +struct process_request_args { + http_server serv; + size_t inc_fd; +}; + +void *process_request(void *args) { + struct process_request_args *pargs = args; + http_server serv = pargs->serv; + size_t inc_fd = pargs->inc_fd; + + arena req_arena = {0}; + + http_request req = {0}; + req.inc_fd = inc_fd; + req.arena = &req_arena; + + http_response resp = {0}; + req.resp = &resp; + + int parse_res = parse_request(&req_arena, inc_fd, &req); + if (parse_res != 0) { + resp.code = parse_res; + http_send(req); + } + + http_context req_context = {0}; + for (size_t i = 0; i < serv.global_ctx.len; i++) { + set_context_value(&req_arena, &req_context, *(serv.global_ctx.data + i)); + } + req.ctx = &req_context; + + http_global_middleware *g_mid = serv.global_middleware; + if (!resp.sent) { + for (size_t i = 0; i < serv.handlers.len; i++) { + http_handler handler = serv.handlers.data[i]; + if (check_handler(handler, req)) { + if (serv.global_middleware != NULL) { + g_mid->end->next = handler.middleware; + g_mid->end->handler = handler.func; + g_mid->start->func(g_mid->start, req); + } else if (handler.middleware != NULL) { + handler.middleware->func(handler.middleware, req); + } else { + handler.func(req); + } + + if (!resp.sent) { + const_string error = CS("Server handled the request but did not send anything\n"); + http_log(HTTP_ERROR, "%s", error.data); + set_context_value(&req_arena, &req_context, (http_context_value){CS("error"), (void *)&error}); + g_mid->end->handler = http_internal_server_error_handler; + g_mid->start->func(g_mid->start, req); + } + break; + } + } + } + + if (!resp.code) { + g_mid->end->handler = http_not_found_handler; + g_mid->start->func(g_mid->start, req); + } + + close(inc_fd); + arena_free(&req_arena); + + pthread_exit(NULL); +} + +int listen_and_serve(http_server *serv) { + if (listen(serv->sockfd, 10) != 0) { + perror("listen"); + return 1; + } + + char my_ipstr[INET6_ADDRSTRLEN]; + inet_ntop(serv->addr->sa_family, get_in_addr(serv->addr), my_ipstr, sizeof my_ipstr); + http_log(HTTP_INFO, "Listening on %s:%d\n", my_ipstr, get_in_port(serv->addr)); + + while (keepRunning) { + struct sockaddr inc_addr; + socklen_t addr_size = sizeof inc_addr; + int inc_fd = accept(serv->sockfd, &inc_addr, &addr_size); + if (inc_fd < 0) { + perror("accept"); + return 1; + } + + struct process_request_args args = { + .serv = *serv, + .inc_fd = inc_fd, + }; + + pthread_t thread_id; + pthread_create(&thread_id, NULL, process_request, (void *)&args); + } + return 0; +} |
