static void http_server_conn_init(HTTP_ServerConn *conn, SocketHandle handle, uint32_t input_buffer_limit, uint32_t output_buffer_limit) { conn->state = HTTP_SERVER_CONN_BUFFERING; conn->handle = handle; conn->closing = false; byte_queue_init(&conn->input, input_buffer_limit); byte_queue_init(&conn->output, output_buffer_limit); } static void http_server_conn_free(HTTP_ServerConn *conn) { byte_queue_free(&conn->output); byte_queue_free(&conn->input); } int http_server_init(HTTP_Server *server) { server->input_buffer_limit = 1<<20; server->output_buffer_limit = 1<<20; server->trace_bytes = false; server->reuse_addr = false; server->backlog = 32; server->num_conns = 0; for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) { server->conns[i].state = HTTP_SERVER_CONN_FREE; server->conns[i].gen = 0; } server->num_ready = 0; server->ready_head = 0; return socket_manager_init(&server->sockets, server->socket_pool, HTTP_SERVER_CAPACITY); } void http_server_free(HTTP_Server *server) { socket_manager_free(&server->sockets); for (int i = 0, j = 0; j < server->num_conns; i++) { HTTP_ServerConn *conn = &server->conns[i]; if (conn->state == HTTP_SERVER_CONN_FREE) continue; j++; http_server_conn_free(conn); } } void http_server_set_input_limit(HTTP_Server *server, uint32_t limit) { server->input_buffer_limit = limit; } void http_server_set_output_limit(HTTP_Server *server, uint32_t limit) { server->output_buffer_limit = limit; } void http_server_set_trace_bytes(HTTP_Server *server, bool value) { server->trace_bytes = value; } void http_server_set_reuse_addr(HTTP_Server *server, bool reuse) { server->reuse_addr = reuse; } void http_server_set_backlog(HTTP_Server *server, int backlog) { server->backlog = backlog; } int http_server_listen_tcp(HTTP_Server *server, HTTP_String addr, Port port) { return socket_manager_listen_tcp(&server->sockets, addr, port, server->backlog, server->reuse_addr); } int http_server_listen_tls(HTTP_Server *server, HTTP_String addr, Port port, HTTP_String cert_file_name, HTTP_String key_file_name) { return socket_manager_listen_tls(&server->sockets, addr, port, server->backlog, server->reuse_addr, cert_file_name, key_file_name); } int http_server_add_certificate(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { return socket_manager_add_certificate(&server->sockets, domain, cert_file, key_file); } int http_server_wakeup(HTTP_Server *server) { return socket_manager_wakeup(&server->sockets); } void http_server_register_events(HTTP_Server *server, EventRegister *reg) { socket_manager_register_events(&server->sockets, reg); } // Look at the head of the input buffer to see if // a request was buffered. If it was, change the // connection's status to WAIT_STATUS and push it // to the ready queue. If the request is invalid, // close the socket. static void check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn) { assert(conn->state == HTTP_SERVER_CONN_BUFFERING); ByteView src = byte_queue_read_buf(&conn->input); int ret = http_parse_request(src.ptr, src.len, &conn->request); if (ret < 0) { // Invalid request byte_queue_read_ack(&conn->input, 0); socket_close(&server->sockets, conn->handle); } else if (ret == 0) { // Still waiting byte_queue_read_ack(&conn->input, 0); // If the queue reached its limit and we still didn't receive // a complete request, abort the exchange. if (byte_queue_full(&conn->input)) socket_close(&server->sockets, conn->handle); } else { // Ready assert(ret > 0); // Stop receiving I/O events while we are building the response socket_silent(&server->sockets, conn->handle, true); conn->state = HTTP_SERVER_CONN_WAIT_STATUS; conn->request_len = ret; conn->response_offset = byte_queue_offset(&conn->output); // Push to the ready queue assert(server->num_ready < HTTP_SERVER_CAPACITY); int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY; server->ready[tail] = conn - server->conns; server->num_ready++; } } static void http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn) { if (conn->state == HTTP_SERVER_CONN_FLUSHING) { ByteView src = byte_queue_read_buf(&conn->output); int num = 0; if (src.len) num = socket_send(&server->sockets, conn->handle, src.ptr, src.len); if (server->trace_bytes) print_bytes(HTTP_STR("<< "), (HTTP_String) { src.ptr, num }); byte_queue_read_ack(&conn->output, num); if (byte_queue_error(&conn->output)) { socket_close(&server->sockets, conn->handle); return; } if (byte_queue_empty(&conn->output)) { // We finished sending the response. Now we can // either close the connection or process a new // buffered request. if (conn->closing) { socket_close(&server->sockets, conn->handle); return; } conn->state = HTTP_SERVER_CONN_BUFFERING; } } if (conn->state == HTTP_SERVER_CONN_BUFFERING) { int min_recv = 1<<10; byte_queue_write_setmincap(&conn->input, min_recv); // Note that it's extra important that we don't // buffer while the user is building the response. // If we did that, a resize would invalidate all // pointers on the parsed request structure. ByteView dst = byte_queue_write_buf(&conn->input); int num = 0; if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); if (server->trace_bytes) print_bytes(HTTP_STR(">> "), (HTTP_String) { dst.ptr, num }); byte_queue_write_ack(&conn->input, num); if (byte_queue_error(&conn->input)) { socket_close(&server->sockets, conn->handle); } else { check_request_buffer(server, conn); } } } void http_server_process_events(HTTP_Server *server, EventRegister reg) { SocketEvent events[HTTP_SERVER_CAPACITY]; int num_events = socket_manager_translate_events(&server->sockets, events, reg); for (int i = 0; i < num_events; i++) { HTTP_ServerConn *conn = events[i].user; if (events[i].type == SOCKET_EVENT_DISCONNECT) { http_server_conn_free(conn); // TODO: what if this was in the ready queue? server->num_conns--; } else if (events[i].type == SOCKET_EVENT_READY) { if (events[i].user == NULL) { if (server->num_conns == HTTP_SERVER_CAPACITY) { socket_close(&server->sockets, events[i].handle); continue; } int j = 0; while (server->conns[j].state != HTTP_SERVER_CONN_FREE) { j++; assert(i < HTTP_SERVER_CAPACITY); } conn = &server->conns[j]; http_server_conn_init(conn, events[i].handle, server->input_buffer_limit, server->output_buffer_limit); server->num_conns++; socket_set_user(&server->sockets, events[i].handle, conn); } while (socket_ready(&server->sockets, events[i].handle) && conn->state != HTTP_SERVER_CONN_WAIT_STATUS) http_server_conn_process_events(server, conn); } } } bool http_server_next_request(HTTP_Server *server, HTTP_Request **request, HTTP_ResponseBuilder *builder) { if (server->num_ready == 0) return false; HTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]]; server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY; server->num_ready--; assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS); *request = &conn->request; *builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen }; return true; } void http_server_wait_request(HTTP_Server *server, HTTP_Request **request, HTTP_ResponseBuilder *builder) { for (;;) { void *ptrs[HTTP_SERVER_POLL_CAPACITY]; struct pollfd polled[HTTP_SERVER_POLL_CAPACITY]; EventRegister reg = { ptrs, polled, 0 }; http_server_register_events(server, ®); if (reg.num_polled > 0) POLL(reg.polled, reg.num_polled, -1); http_server_process_events(server, reg); if (http_server_next_request(server, request, builder)) break; } } // Get a connection pointer from a response builder. // If the builder is invalid, returns NULL. // Note that only connections in the responding states // can be returned, as any builder is invalidated by // incrementing the connection's generation counter // when a response is completed. static HTTP_ServerConn* builder_to_conn(HTTP_ResponseBuilder builder) { HTTP_Server *server = builder.server; if (server == NULL) return NULL; if (builder.index > HTTP_SERVER_CAPACITY) return NULL; HTTP_ServerConn *conn = &server->conns[builder.index]; if (builder.gen != conn->gen) return NULL; return conn; } static const char* get_status_text(int code) { switch(code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 102: return "Processing"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 207: return "Multi-Status"; case 208: return "Already Reported"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 306: return "Switch Proxy"; case 307: return "Temporary Redirect"; case 308: return "Permanent Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; case 417: return "Expectation Failed"; case 418: return "I'm a teapot"; case 420: return "Enhance your calm"; case 422: return "Unprocessable Entity"; case 426: return "Upgrade Required"; case 429: return "Too many requests"; case 431: return "Request Header Fields Too Large"; case 449: return "Retry With"; case 451: return "Unavailable For Legal Reasons"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; case 509: return "Bandwidth Limit Exceeded"; } return "???"; } static void write_status(HTTP_ServerConn *conn, int status) { byte_queue_write_fmt(&conn->output, "HTTP/1.1 %d %s\r\n", status, get_status_text(status)); } void http_response_builder_status(HTTP_ResponseBuilder builder, int status) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) { // Reset all response content and start from scrach. byte_queue_remove_from_offset(&conn->output, conn->response_offset); conn->state = HTTP_SERVER_CONN_WAIT_STATUS; } write_status(conn, status); conn->state = HTTP_SERVER_CONN_WAIT_HEADER; } static bool is_header_valid(HTTP_String str) { bool has_colon = false; for (int i = 0; i < str.len; i++) { char c = str.ptr[i]; if (c == ':') has_colon = true; // Reject control characters (especially \r and \n) if (c < 0x20 && c != '\t') return false; } return has_colon; } void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) return; // Header must contain a colon and no control characters // to prevent HTTP response splitting attacks if (!is_header_valid(str)) return; // Silently drop it byte_queue_write(&conn->output, str.ptr, str.len); byte_queue_write(&conn->output, "\r\n", 2); } static void append_special_headers(HTTP_ServerConn *conn) { HTTP_String s; if (conn->closing) { s = HTTP_STR("Connection: Close\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); } else { s = HTTP_STR("Connection: Keep-Alive\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); } s = HTTP_STR("Content-Length: "); byte_queue_write(&conn->output, s.ptr, s.len); conn->content_length_value_offset = byte_queue_offset(&conn->output); #define TEN_SPACES " " _Static_assert(sizeof(TEN_SPACES) == 10+1); s = HTTP_STR(TEN_SPACES "\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); byte_queue_write(&conn->output, "\r\n", 2); conn->content_length_offset = byte_queue_offset(&conn->output); } static void patch_special_headers(HTTP_ServerConn *conn) { int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset); char tmp[11]; int len = snprintf(tmp, sizeof(tmp), "%d", content_length); assert(len > 0 && len < 11); byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len); } void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) return; byte_queue_write(&conn->output, str.ptr, str.len); } void http_response_builder_body_cap(HTTP_ResponseBuilder builder, int cap) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) return; byte_queue_write_setmincap(&conn->output, cap); } char *http_response_builder_body_buf(HTTP_ResponseBuilder builder, int *cap) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return NULL; if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) return NULL; ByteView tmp = byte_queue_write_buf(&conn->output); *cap = tmp.len; return tmp.ptr; } void http_response_builder_body_ack(HTTP_ResponseBuilder builder, int num) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) return; byte_queue_write_ack(&conn->output, num); } void http_response_builder_send(HTTP_ResponseBuilder builder) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) { write_status(conn, 500); conn->state = HTTP_SERVER_CONN_WAIT_HEADER; } if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY); patch_special_headers(conn); // Remove the buffered request byte_queue_read_ack(&conn->input, conn->request_len); conn->state = HTTP_SERVER_CONN_FLUSHING; conn->gen++; // Enable back I/O events socket_silent(&builder.server->sockets, conn->handle, false); http_server_conn_process_events(builder.server, conn); }