inline static void http1_free(http1_protocol_s* http) { http_request_clear(&http->request); validate_mem(); if (((void*)http) >= http1_pool.memory && ((void*)http) <= (http1_pool.memory + HTTP1_POOL_MEMORY_SIZE)) { pool_push(http); } else free(http); }
protocol_s* http1_alloc(intptr_t fd, http_settings_s* settings) { validate_mem(); // HTTP/1.1 should send a busy response // if there aren't enough available file descriptors. if (sock_max_capacity() - sock_uuid2fd(fd) <= HTTP_BUSY_UNLESS_HAS_FDS) goto is_busy; // get an http object from the pool http1_protocol_s* http = pool_pop(); // of malloc one if (http == NULL) http = malloc(HTTP1_PROTOCOL_SIZE); // review allocation if (http == NULL) return NULL; // we shouldn't update the `http` protocol as a struct, as this will waste // time as the whole buffer will be zeroed out when there is no need. // setup parsing state http->buffer_pos = 0; // setup protocol callbacks http->protocol = (protocol_s){ .service = HTTP1, .on_data = (void (*)(intptr_t, protocol_s*))http1_on_data, .on_close = (void (*)(protocol_s*))http1_free, }; // setup request data http->request = (http_request_s){ .metadata.max_headers = HTTP1_MAX_HEADER_COUNT, .metadata.fd = fd, .metadata.owner = http, }; // update settings http->settings = settings; http->on_request = settings->on_request; // set the timeout server_set_timeout(fd, settings->timeout); return (protocol_s*)http; is_busy: if (settings->public_folder && settings->public_folder_length) { size_t p_len = settings->public_folder_length; struct stat file_data = {}; char fname[p_len + 8 + 1]; memcpy(fname, settings->public_folder, p_len); if (settings->public_folder[p_len - 1] == '/' || settings->public_folder[p_len - 1] == '\\') p_len--; memcpy(fname + p_len, "/503.html", 9); p_len += 9; if (stat(fname, &file_data)) goto busy_no_file; // check that we have a file and not something else if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode)) goto busy_no_file; int file = open(fname, O_RDONLY); if (file == -1) goto busy_no_file; sock_packet_s* packet; packet = sock_checkout_packet(); memcpy(packet->buffer, "HTTP/1.1 503 Service Unavailable\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n" "Content-Length: ", 94); p_len = 94 + http_ul2a(packet->buffer + 94, file_data.st_size); memcpy(packet->buffer + p_len, "\r\n\r\n", 4); p_len += 4; if (BUFFER_PACKET_SIZE - p_len > file_data.st_size) { if (read(file, packet->buffer + p_len, file_data.st_size) < 0) { close(file); sock_free_packet(packet); goto busy_no_file; } close(file); packet->length = p_len + file_data.st_size; sock_send_packet(fd, packet); } else { packet->length = p_len; sock_send_packet(fd, packet); sock_sendfile(fd, file, 0, file_data.st_size); } return NULL; } busy_no_file: sock_write(fd, "HTTP/1.1 503 Service Unavailable\r\nContent-Length: " "13\r\n\r\nServer Busy.", 68); return NULL; } /* ***************************************************************************** HTTP/1.1 protocol bare-bones implementation */ #define HTTP_BODY_CHUNK_SIZE 3072 // 4096 /* parse and call callback */ static void http1_on_data(intptr_t uuid, http1_protocol_s* protocol) { ssize_t len = 0; ssize_t result; char buff[HTTP_BODY_CHUNK_SIZE]; char* buffer; http_request_s* request = &protocol->request; for (;;) { // handle requests with no file data if (request->body_file <= 0) { // request headers parsing if (len == 0) { buffer = protocol->buffer; // make sure headers don't overflow len = sock_read(uuid, buffer + protocol->buffer_pos, HTTP1_MAX_HEADER_SIZE - protocol->buffer_pos); // update buffer read position. protocol->buffer_pos += len; } if (len <= 0) { return; } // parse headers result = http1_parse_request_headers(buffer, protocol->buffer_pos, request); // review result if (result >= 0) { // headers comeplete // mark buffer position, for HTTP pipelining protocol->buffer_pos = result; // are we done? if (request->content_length == 0 || request->body_str) { goto handle_request; } if (request->content_length > protocol->settings->max_body_size) { goto body_to_big; } // initialize or submit body data result = http1_parse_request_body(buffer + result, len - result, request); if (result >= 0) { protocol->buffer_pos += result; goto handle_request; } else if (result == -1) // parser error goto parser_error; goto parse_body; } else if (result == -1) // parser error goto parser_error; // assume incomplete (result == -2), even if wrong, we're right. len = 0; continue; } if (request->body_file > 0) { parse_body: buffer = buff; // request body parsing len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE); if (len <= 0) return; result = http1_parse_request_body(buffer, len, request); if (result >= 0) { // set buffer pos for piplining support protocol->buffer_pos = result; goto handle_request; } else if (result == -1) // parser error goto parser_error; if (len < HTTP_BODY_CHUNK_SIZE) // pause parser for more data return; goto parse_body; } continue; handle_request: // review required headers / data if (request->host == NULL) goto bad_request; http_settings_s* settings = protocol->settings; // call request callback if (protocol && settings && (protocol->settings->public_folder == NULL || http_response_sendfile2(NULL, request, settings->public_folder, settings->public_folder_length, request->path, request->path_len, settings->log_static))) { protocol->on_request(request); } // clear request state http_request_clear(request); // rotate buffer for HTTP pipelining if (result >= len) { len = 0; } else { memmove(protocol->buffer, buffer + protocol->buffer_pos, len - result); len -= result; } // restart buffer position protocol->buffer_pos = 0; buffer = protocol->buffer; } // no routes lead here. fprintf(stderr, "I am lost on a deserted island, no code can reach me here :-)\n"); return; // How did we get here? parser_error: if (request->headers_count == request->metadata.max_headers) goto too_big; bad_request: /* handle generally bad requests */ { http_response_s response = http_response_init(request); response.status = 400; http_response_write_body(&response, "Bad Request", 11); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; } too_big: /* handle oversized headers */ { http_response_s response = http_response_init(request); response.status = 431; http_response_write_body(&response, "Request Header Fields Too Large", 31); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; body_to_big: /* handle oversized body */ { http_response_s response = http_response_init(request); response.status = 413; http_response_write_body(&response, "Payload Too Large", 17); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; } } } /* ***************************************************************************** HTTP/1.1 listenning API implementation */ #undef http1_listen static void http1_on_init(http_settings_s* settings) { if (settings->timeout == 0) settings->timeout = 5; if (settings->max_body_size == 0) settings->max_body_size = HTTP_DEFAULT_BODY_LIMIT; if (settings->public_folder) { settings->public_folder_length = strlen(settings->public_folder); if (settings->public_folder[0] == '~' && settings->public_folder[1] == '/' && getenv("HOME")) { char* home = getenv("HOME"); size_t home_len = strlen(home); char* tmp = malloc(settings->public_folder_length + home_len + 1); memcpy(tmp, home, home_len); if (home[home_len - 1] == '/') --home_len; memcpy(tmp + home_len, settings->public_folder + 1, settings->public_folder_length); // copy also the NULL settings->public_folder = tmp; settings->private_metaflags |= 1; settings->public_folder_length = strlen(settings->public_folder); } } } static void http1_on_finish(http_settings_s* settings) { if (settings->private_metaflags & 1) free((void*)settings->public_folder); if (settings->private_metaflags & 2) free(settings); } int http1_listen(const char* port, const char* address, http_settings_s settings) { if (settings.on_request == NULL) { fprintf( stderr, "ERROR: http1_listen requires the .on_request parameter to be set\n"); exit(11); } http_settings_s* settings_copy = malloc(sizeof(*settings_copy)); *settings_copy = settings; settings_copy->private_metaflags = 2; return server_listen(.port = port, .address = address, .on_start = (void*)http1_on_init, .on_finish = (void*)http1_on_finish, .on_open = (void*)http1_alloc, .udata = settings_copy); }
void *handle_connection(void *arg) { st_netfd_t client_nfd = (st_netfd_t)arg; struct http_stream *s = http_stream_create(HTTP_SERVER, SEC2USEC(5)); char buf[4*1024]; int error = 0; struct http_stream *cs = NULL; uri_t *u = uri_new(); int should_close = 1; for (;;) { should_close = 1; if (s->status != HTTP_STREAM_OK) break; cs = NULL; error = 0; s->timeout = SEC2USEC(5); int status = http_stream_request_read(s, client_nfd); s->timeout = SEC2USEC(30); // longer timeout for the rest if (status != HTTP_STREAM_OK) { if (s->status == HTTP_STREAM_CLOSED || s->status == HTTP_STREAM_TIMEOUT) { error = 1; } else { error = 400; } goto release; } cs = http_stream_create(HTTP_CLIENT, SEC2USEC(30)); //http_request_debug_print(s->req); fprintf(stderr, "request uri: %s\n", s->req->uri); const char *error_at = NULL; uri_clear(u); if (uri_parse(u, s->req->uri, strlen(s->req->uri), &error_at) == 0) { fprintf(stderr, "uri_parse error: %s\n", error_at); error = 400; goto release; } uri_normalize(u); if (http_stream_connect(cs, u->host, u->port) != HTTP_STREAM_OK) { error = 504; goto release; } http_request_header_remove(s->req, "Accept-Encoding"); http_request_header_remove(s->req, "Proxy-Connection"); /* TODO: need to expose a copy api for http message */ http_request_t *tmp_req = cs->req; cs->req = s->req; char *request_uri = uri_compose_partial(u); char *tmp_uri = s->req->uri; cs->req->uri = request_uri; if (http_stream_request_send(cs) != HTTP_STREAM_OK) { error = 504; goto release; } cs->req = tmp_req; s->req->uri = tmp_uri; free(request_uri); /* TODO: fix this. post might not contain data. probably move this logic into stream */ size_t total = 0; if (g_strcmp0("POST", s->req->method) == 0) { for (;;) { ssize_t nr = sizeof(buf); status = http_stream_read(s, buf, &nr); fprintf(stderr, "server http_stream_read nr: %zd\n", nr); if (nr < 0 || status != HTTP_STREAM_OK) { error = 1; goto release; } if (nr == 0) break; /*fwrite(buf, sizeof(char), nr, stdout);*/ ssize_t nw = st_write(cs->nfd, buf, nr, s->timeout); if (nw != nr) { error=1; goto release; } fprintf(stderr, "st_write nw: %zd\n", nr); total += nr; } fprintf(stderr, "http_stream_read total: %zu\n", total); } if (http_stream_response_read(cs) != HTTP_STREAM_OK) { error = 502; goto release; } /* TODO: properly create a new response and copy headers */ http_response_t *tmp_resp = s->resp; s->resp = cs->resp; s->resp->http_version = "HTTP/1.1"; http_response_header_remove(s->resp, "Content-Length"); http_response_header_remove(s->resp, "Transfer-Encoding"); if (s->resp->status_code != 204) http_response_header_append(s->resp, "Transfer-Encoding", "chunked"); ssize_t nw = http_stream_response_send(s, 0); s->resp = tmp_resp; fprintf(stderr, "http_stream_response_send: %zd\n", nw); if (s->resp->status_code != 204 && (cs->content_size > 0 || cs->transfer_encoding == TE_CHUNKED)) { total = 0; fprintf(stderr, "content size: %zd\n", cs->content_size); for (;;) { ssize_t nr = sizeof(buf); status = http_stream_read(cs, buf, &nr); fprintf(stderr, "client http_stream_read nr: %zd\n", nr); if (nr <= 0 || status != HTTP_STREAM_OK) break; /*fwrite(buf, sizeof(char), nr, stdout);*/ total += nr; if (http_stream_send_chunk(s, buf, nr) != HTTP_STREAM_OK) break; } fprintf(stderr, "written to client: %zu\n", total); if (total > 0 && s->status == HTTP_STREAM_OK) { http_stream_send_chunk_end(s); } else { fprintf(stderr, "for request: %s status: %d\n", s->req->uri, s->status); } } release: if (!error) { if ((g_strcmp0("HTTP/1.1", s->req->http_version) == 0) && (g_strcmp0(http_request_header_getstr(s->req, "Connection"), "close") != 0)) { // if HTTP/1.1 client and no Connection: close, then don't close should_close = 0; } else if (g_strcmp0(http_request_header_getstr(s->req, "Connection"), "keepalive") == 0) { should_close = 0; } } http_request_clear(s->req); uri_clear(u); if (cs) http_stream_close(cs); /* TODO: break loop if HTTP/1.0 and not keep-alive */ if (error) { fprintf(stderr, "ERROR: %d STATUS: %d, exiting\n", error, s->status); /* TODO: use reason string */ if (error >= 400 && s->status != HTTP_STREAM_CLOSED) { http_response_free(s->resp); s->resp = http_response_new(error, "Error"); http_response_header_append(s->resp, "Content-Length", "0"); s->status = HTTP_STREAM_OK; /* TODO: might want to move this logic into http_stream */ http_stream_response_send(s, 0); } break; } if (should_close) break; } fprintf(stderr, "exiting handle_connection (should_close: %u)\n", should_close); uri_free(u); http_stream_close(s); return NULL; }