httpp-benchmark

httpp-benchmark.git
git clone git://git.lenczewski.org/httpp-benchmark.git
Log | Files | Refs | README | LICENSE

commit 35c7a58d92cfa79a848def69671ce95b79a5a874
parent 4b03072593e015c1eef76988a7383db9fa5f2a5c
Author: cebem1nt <mineewarik@gmail.com>
Date:   Fri, 21 Nov 2025 22:16:57 -0300

Moving basic parser to the header, small error handling improvements, etc

Diffstat:
Mhttpp.h | 240++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Dimp.c | 226-------------------------------------------------------------------------------
Atest.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 298 insertions(+), 227 deletions(-)

diff --git a/httpp.h b/httpp.h @@ -4,6 +4,26 @@ */ #include <stddef.h> +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define HTTP_DELIMETER "\r\n" +#define HTTP_DELIMETER_SIZE 2 +#define HTTP_VERSION "HTTP/1.1" + +#define _HTTPP_LINE_BUFSIZE 4096 +#define _HTTPP_INITIAL_HEADERS_ARR_CAP 20 + +#define _HTTPP_MAX_METHOD_LENGTH (10 + 1) +#define _HTTPP_VERISON_BUFSIZE (10 + 1) + +#define nomem() do { fprintf(stderr, "No memory, see ya!\n"); exit(1); } while (0) +#define http_string_to_method(s) (strcmp(s, "GET") == 0 ? 0 : \ + strcmp(s, "POST") == 0 ? 1 : \ + strcmp(s, "DELETE") == 0 ? 2 : -1) typedef enum { GET, @@ -29,9 +49,227 @@ typedef struct { char* body; } http_req_t; -http_req_t* http_parse_request(char* raw); +const char* httpp_method_to_string(http_method_t m); +http_req_t* httpp_req_new(); +http_req_t* httpp_parse_request(char* raw); + +int httpp_parse_header(http_headers_arr_t* hs, char* line); +void httpp_headers_append(http_headers_arr_t* hs, http_header_t header); +void httpp_req_free(http_req_t* req); + #ifdef LIBPTTH_IMPLEMENTATION +#define ltrim(str) \ + while(*(str) && isspace(*(str))) { \ + (str)++; \ + } + +#define rtrim(str) do { \ + char* end = (str) + strlen(str) - 1; \ + while (end >= (str) && isspace(*end)) { \ + *end = '\0'; \ + --end; \ + } \ + } while (0) + +static inline void* emalloc(size_t size) +{ + void* ptr = malloc(size); + if (!ptr) + nomem(); + + return ptr; +} + +static inline void* erealloc(void* ptr, size_t size) +{ + void* new_ptr = realloc(ptr, size); + if (!new_ptr) + nomem(); + + return ptr; +} + +static int chop_until(char c, char** src, char* dest, size_t n) +{ + char* pos = strchr(*src, c); + if (!pos) + return 1; + + size_t chopped_size = pos - *src; + if (chopped_size >= n) + return 1; + + memcpy(dest, *src, chopped_size); + dest[chopped_size] = '\0'; + + *src = pos + 1; + return 0; +} + +static char* dchop_until(char c, char** src) +{ + char* pos = strchr(*src, c); + if (!pos) + return NULL; + + size_t chopped_size = pos - *src; + char* out = (char*) emalloc(chopped_size + 1); + + memcpy(out, *src, chopped_size); + out[chopped_size] = '\0'; + + *src = pos + 1; + return out; +} + +const char* httpp_method_to_string(http_method_t m) +{ + switch (m) { + case GET: return "GET"; + case POST: return "POST"; + case DELETE: return "DELETE"; + default: return "UNKNOWN"; + } +} + +http_req_t* httpp_req_new() +{ + http_req_t* out = (http_req_t*) emalloc(sizeof(http_req_t)); + + out->headers = (http_headers_arr_t*) emalloc(sizeof(http_headers_arr_t)); + out->headers->arr = (http_header_t*) emalloc(sizeof(http_header_t) * _HTTPP_INITIAL_HEADERS_ARR_CAP); + out->headers->capacity = _HTTPP_INITIAL_HEADERS_ARR_CAP; + out->headers->length = 0; + + return out; +} + +void httpp_req_free(http_req_t* req) +{ + if (!req) return; + + for (size_t i = 0; i < req->headers->length; i++) { + free(req->headers->arr[i].name); + free(req->headers->arr[i].value); + } + + free(req->headers->arr); + free(req->route); + free(req->body); + free(req); +} + +void httpp_headers_append(http_headers_arr_t* hs, http_header_t header) +{ + if (hs->length >= hs->capacity) { + size_t new_cap = hs->capacity * 2; + hs->arr = (http_header_t*) erealloc(hs->arr, new_cap * sizeof(http_header_t)); + hs->capacity = new_cap; + } + + hs->arr[hs->length++] = header; +} + +int httpp_parse_header(http_headers_arr_t* hs, char* line) +{ + char* delim_pos = strchr(line, ':'); + if (!delim_pos) + return 1; + + size_t name_len = (delim_pos - line); + if (name_len == 0) + return 2; + + char* name = (char*) emalloc(name_len + 1); + memcpy(name, line, name_len); + name[name_len] = '\0'; + + char* value_start = delim_pos + 1; + ltrim(value_start); + + char* value = strdup(value_start); + if (!value) + nomem(); + + httpp_headers_append(hs, (http_header_t){name, value}); + return 0; +} + +static int http_parse_start_line(char** itr, http_req_t* dest) +{ + char method_buf[_HTTPP_MAX_METHOD_LENGTH]; + char version_buf[_HTTPP_VERISON_BUFSIZE]; + + ltrim(*itr); + + if (chop_until(' ', itr, method_buf, _HTTPP_MAX_METHOD_LENGTH) != 0) + return 1; + + ltrim(*itr) // Route might have extra spaces at the bginning, for our implementation thats fine + char* route = dchop_until(' ', itr); + + ltrim(*itr); + if (chop_until('\n', itr, version_buf, _HTTPP_VERISON_BUFSIZE) != 0) { + free(route); + return 1; + } + + rtrim(version_buf); + + dest->method = (http_method_t) http_string_to_method(method_buf); + dest->route = route; + + if (strcmp(version_buf, HTTP_VERSION) != 0) { + free(route); + return 1; + } + + return 0; +} + +http_req_t* httpp_parse_request(char* raw) +{ + http_req_t* out = httpp_req_new(); + + char* itr = raw; + char* end = raw + strlen(raw); + char line[_HTTPP_LINE_BUFSIZE]; + + if (http_parse_start_line(&itr, out) != 0) + return NULL; + + while (itr < end) { + char* del_pos = strstr(itr, HTTP_DELIMETER); + if (!del_pos) + break; + + size_t line_size = del_pos - itr; + if (line_size == 0) { + itr = del_pos + HTTP_DELIMETER_SIZE; + break; + } + + if (line_size >= _HTTPP_LINE_BUFSIZE) { + free(out); + return NULL; + } + + memcpy(line, itr, line_size); + line[line_size] = '\0'; + + if (httpp_parse_header(out->headers, line) != 0) + return NULL; + + itr = del_pos + HTTP_DELIMETER_SIZE; + } + // TODO, there is a thing called chunked transfer, which is + // definitely not handled well by this parser + out->body = strdup(itr); + if (!out->body) + return NULL; + return out; +} #endif // LIBPTTH_IMPLEMENTATION \ No newline at end of file diff --git a/imp.c b/imp.c @@ -1,225 +0,0 @@ -#include <ctype.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "httpp.h" - -#define HTTP_DELIMETER "\r\n" -#define HTTP_DELIMETER_SIZE 2 -#define HTTP_VERSION "HTTP/1.1" - -#define LINE_BUFSIZE 4096 -#define INITIAL_HEADERS_ARR_CAP 20 - -#define MAX_METHOD_LENGTH (10 + 1) -#define VERISON_BUFSIZE (10 + 1) - -#define nomem() do { fprintf(stderr, "No memory, see ya!\n"); exit(1); } while (0) -#define http_string_to_method(s) (strcmp(s, "GET") == 0 ? 0 : \ - strcmp(s, "POST") == 0 ? 1 : \ - strcmp(s, "DELETE") == 0 ? 2 : -1) - -#define ltrim(str) \ - while(*(str) && isspace(*(str))) { \ - (str)++; \ - } - -#define rtrim(str) do { \ - char* end = (str) + strlen(str) - 1; \ - while (end >= (str) && isspace(*end)) { \ - *end = '\0'; \ - --end; \ - } \ - } while (0) - -static inline void* emalloc(size_t size) -{ - void* ptr = malloc(size); - if (!ptr) - nomem(); - - return ptr; -} - -static inline void* erealloc(void* ptr, size_t size) -{ - void* new_ptr = realloc(ptr, size); - if (!new_ptr) - nomem(); - - return ptr; -} - -static void chop_until(char c, char** src, char* dest, size_t n) -{ - char* pos = strchr(*src, c); - if (!pos) - return; - - size_t chopped_size = pos - *src; - if (chopped_size >= n) - return; - - memcpy(dest, *src, chopped_size); - dest[chopped_size] = '\0'; - - *src = pos + 1; -} - -static char* dchop_until(char c, char** src) -{ - char* pos = strchr(*src, c); - if (!pos) - return NULL; - - size_t chopped_size = pos - *src; - char* dest = emalloc(chopped_size + 1); - - memcpy(dest, *src, chopped_size); - dest[chopped_size] = '\0'; - - *src = pos + 1; - return dest; -} - -const char* http_method_to_string(http_method_t m) -{ - switch (m) { - case GET: return "GET"; - case POST: return "POST"; - case DELETE: return "DELETE"; - default: return "UNKNOWN"; - } -} - -http_req_t* http_req_new() -{ - http_req_t* new = emalloc(sizeof(http_req_t)); - - new->headers = emalloc(sizeof(http_headers_arr_t)); - new->headers->arr = emalloc(sizeof(http_header_t) * INITIAL_HEADERS_ARR_CAP); - new->headers->capacity = INITIAL_HEADERS_ARR_CAP; - new->headers->length = 0; - - return new; -} - -void http_headers_append(http_headers_arr_t* hs, http_header_t header) -{ - if (hs->length >= hs->capacity) { - size_t new_cap = hs->capacity * 2; - hs->arr = erealloc(hs->arr, new_cap * sizeof(http_header_t)); - hs->capacity = new_cap; - } - - hs->arr[hs->length++] = header; -} - -void http_parse_header(http_headers_arr_t* hs, char* line) -{ - char* delim_pos = strchr(line, ':'); - if (!delim_pos) - return; - - size_t name_len = (delim_pos - line); - if (name_len == 0) - return; - - char* name = emalloc(name_len + 1); - memcpy(name, line, name_len); - name[name_len] = '\0'; - - char* value_start = delim_pos + 1; - ltrim(value_start); - - char* value = strdup(value_start); - if (!value) - nomem(); - - http_headers_append(hs, (http_header_t){name, value}); -} - -void http_parse_start_line(char** itr, http_req_t* dest) -{ - char method_buf[MAX_METHOD_LENGTH]; - char version_buf[VERISON_BUFSIZE]; - - ltrim(*itr); - - chop_until(' ', itr, method_buf, MAX_METHOD_LENGTH); - char* route = dchop_until(' ', itr); - - ltrim(*itr) // Route might have extra spaces at the bginning, for our implementation thats fine - chop_until('\n', itr, version_buf, VERISON_BUFSIZE); - rtrim(version_buf); - - dest->method = http_string_to_method(method_buf); - dest->route = route; - - if (strcmp(version_buf, HTTP_VERSION) != 0) { - fprintf(stderr, "Unsupported protocol version\n"); - exit(1); - } -} - -http_req_t* http_parse_request(char* raw) -{ - http_req_t* out = http_req_new(); - - char* itr = raw; - char* end = raw + strlen(raw); - char line[LINE_BUFSIZE]; - - http_parse_start_line(&itr, out); - - while (itr < end) { - char* del_pos = strstr(itr, HTTP_DELIMETER); - if (!del_pos) - break; - - size_t line_size = del_pos - itr; - if (line_size >= LINE_BUFSIZE) - return NULL; - - memcpy(line, itr, line_size); - line[line_size] = '\0'; - - http_parse_header(out->headers, line); - itr = del_pos + HTTP_DELIMETER_SIZE; - } - - out->body = strdup(itr); - return out; -} - -int main() -{ - char* req = - "POST /api/items HTTP/1.1\r\n" - "Host: api.example.com\r\n" - "User-Agent: MyClient/1.0\r\n" - "Content-Type: application/json\r\n" - "Content-Length: 48\r\n" - "\r\n" - "{\"name\":\"Widget\",\"quantity\":10,\"price\":9.99}"; - - http_req_t* parsed = http_parse_request(req); - - printf("Method: %i (%s)\n", parsed->method, http_method_to_string(parsed->method)); - printf("Route: %s\n", parsed->route); - - printf("Parsed headers length: %lu\n", parsed->headers->length); - for (size_t i = 0; i < parsed->headers->length; i++) { - printf("Header %lu:\n", i); - printf(" Name: :%s:\n", parsed->headers->arr[i].name); - printf(" Value: :%s:\n", parsed->headers->arr[i].value); - } - - printf("\n--- Parsed body: ---\n"); - printf("%s\n", parsed->body); - printf("----------------------\n"); - - return 0; -} -\ No newline at end of file diff --git a/test.c b/test.c @@ -0,0 +1,58 @@ +#include <stdio.h> + +#define LIBPTTH_IMPLEMENTATION +#include "httpp.h" + +void print(http_req_t* parsed) +{ + if (parsed == NULL) { + printf("Couldn't parse!\n"); + return; + } + + printf("Method: %i (%s)\n", parsed->method, httpp_method_to_string(parsed->method)); + printf("Route: %s\n", parsed->route); + + printf("Parsed headers length: %lu\n", parsed->headers->length); + for (size_t i = 0; i < parsed->headers->length; i++) { + printf("Header %lu:\n", i); + printf(" Name: :%s:\n", parsed->headers->arr[i].name); + printf(" Value: :%s:\n", parsed->headers->arr[i].value); + } + + printf("\n---- Parsed body: ----\n"); + printf("%s\n", parsed->body); + printf("-------- (%lu) --------\n", strlen(parsed->body)); +} + +int main() +{ + char* req1 = + "POST /api/items HTTP/1.1 \r\n" + "Host: api.example.com\r\n" + "User-Agent: MyClient/1.0\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 48\r\n" + "\r\n" + "{\"name\":\"Widget\",\"quantity\":10,\"price\":9.99}"; + + http_req_t* parsed = httpp_parse_request(req1); + print(parsed); + httpp_req_free(parsed); + + char* req2 = + "POST /api/items HTTP/1.1\r\n" + "Host: api.example.com\r\n" + "User-Agent: MyClient/1.0\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 106\r\n" + "X-Trace-ID: ;;--TRACE--;;\r\n" + "X-Feature-Flags: ,enable-new, ,\r\n" + "\r\n" + "{\"name\":\"SpicyWidget\",\"quantity\":-1,\"price\":9.9900,\"tags\":[\"hot\",\"ßpecial\",\"null\",null],\"meta\":{\"note\":\"line1\\nline2\\r\\nline3\",\"empty\":\"\",\"unicode\":\"🔥🚀\",\"quote_test\":\"She said: \\\"Spicy!\\\"\"}}"; + + parsed = httpp_parse_request(req2); + print(parsed); + + return 0; +} +\ No newline at end of file