commit cb449cfa2b92fbdd397e35cc39656b33510d19b1
parent 46146f58b1af7185f32f03cf1afa97eb17594e2b
Author: cebem1nt <mineewarik@gmail.com>
Date: Sat, 29 Nov 2025 13:44:26 -0300
Added README, benchmark graphs, example.c and overall improvements
Diffstat:
| M | .gitignore | | | 4 | ++-- |
| M | README.md | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| A | bench.c | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | benchmark.c | | | 52 | ---------------------------------------------------- |
| A | elapsed.svg | | | 2 | ++ |
| A | example.c | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
| M | httpp.h | | | 51 | ++++++++++++++++++++++++++++++++++++++++----------- |
| A | requests.svg | | | 2 | ++ |
| M | test.c | | | 10 | +++++----- |
9 files changed, 217 insertions(+), 72 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,2 @@
*.out
-benchmark
-\ No newline at end of file
+bench
+\ No newline at end of file
diff --git a/README.md b/README.md
@@ -1,3 +1,65 @@
# httpp
+Tiny, simple, yet useful header only dynamic HTTP/1.1 requests parser for C.
-WIP
-\ No newline at end of file
+## API
+
+```c
+#include <stdio.h>
+#include <stdlib.h>
+
+#define HTTPP_IMPLEMENTATION
+#include "httpp.h"
+
+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}";
+
+int main()
+{
+ httpp_req_t* parsed = httpp_parse_request(req);
+ printf("%s\n", parsed->body); // parsed, malloc'd body
+ printf("%i\n", parsed->method); // Method is an enum!
+
+ const char* method = httpp_method_to_string(parsed->method); // Method as string (e.g POST)
+ printf("%s\n", method);
+
+ httpp_header_t* host = httpp_find_header(parsed, "Host");
+ printf("%s\n", host->value); // api.example.com
+
+ httpp_res_t* response = httpp_res_new();
+ response->code = Ok; // or 200
+ httpp_add_header(response, "Host", "somehost.some.where");
+ httpp_add_header(response, "Status", "ok");
+
+ httpp_res_set_body(response, "Some body");
+ char* raw = httpp_res_to_raw(response); // Convert to a malloc'd raw string
+
+ printf("\nComposed response: \n");
+ printf("----\n%s\n----\n", raw); // Or write it to a socket
+
+ // Don't forget to free everything
+ httpp_req_free(parsed); // Free parsed request
+ httpp_res_free(response); // Free response structure
+ free(raw); // Free composed raw response
+}
+```
+
+## Benchmark
+All benchmarks were compiled with -O3 flag using gcc _15.2.1 20251112_. Benchmarks were running on a Ryzen 7 with 4.79GHz peek frequency. The code can be found [here](./bench.c). Same benchmark was adapted for http-parser. Results of each one is the average of 5 runs
+
+
+
+| http-parser | httpp | picohttpparser |
+| ----------- | ----------- | -------------- |
+| `5.344527s` | `3.058964s` | `2.005615s` |
+
+
+
+| http-parser | httpp | picohttpparser |
+| ------------- | ------------- | -------------- |
+| `1871702.306` | `3269700.736` | `4986831.824` |
+\ No newline at end of file
diff --git a/bench.c b/bench.c
@@ -0,0 +1,58 @@
+// A benchmark adapted from https://github.com/h2o/picohttpparser
+
+#include <assert.h>
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+
+#define HTTPP_IMPLEMENTATION
+#include "httpp.h"
+
+#define REQ \
+"GET /cookies HTTP/1.1\r\n" \
+"Host: 127.0.0.1:8090\r\n" \
+"Connection: keep-alive\r\n" \
+"Cache-Control: max-age=0\r\n" \
+"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" \
+"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17\r\n" \
+"Accept-Encoding: gzip,deflate,sdch\r\n" \
+"Accept-Language: en-US,en;q=0.8\r\n" \
+"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n" \
+"Cookie: name=wookie\r\n" \
+"\r\n"
+
+#define ITERATIONS 10000000
+
+double now_seconds()
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec + ts.tv_nsec / 1e9;
+}
+
+int main()
+{
+ char* raw = REQ;
+ size_t raw_len = strlen(raw);
+ int i;
+
+ double t0 = now_seconds();
+ for (i = 0; i < ITERATIONS; ++i) {
+ httpp_req_t* req = httpp_parse_request(raw);
+ if (!req) {
+ fprintf(stderr, "parse failed at iter %d\n", i);
+ return 1;
+ }
+
+ httpp_req_free(req);
+ }
+ double t1 = now_seconds();
+
+ double elapsed = t1 - t0;
+ double reqs_per_second = (double) ITERATIONS / elapsed;
+
+ printf("Elapsed %f seconds.\n", elapsed);
+ printf("Requests per second ≈ %.2f \n", reqs_per_second);
+
+ return 0;
+}
diff --git a/benchmark.c b/benchmark.c
@@ -1,52 +0,0 @@
-// A benchmark adapted from https://github.com/fukamachi/fast-http
-
-#include <assert.h>
-#include <stdio.h>
-#include <time.h>
-#include <string.h>
-
-#define HTTPP_IMPLEMENTATION
-#include "httpp.h"
-
-#define REQ \
-"GET /cookies HTTP/1.1\r\n" \
-"Host: 127.0.0.1:8090\r\n" \
-"Connection: keep-alive\r\n" \
-"Cache-Control: max-age=0\r\n" \
-"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" \
-"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17\r\n" \
-"Accept-Encoding: gzip,deflate,sdch\r\n" \
-"Accept-Language: en-US,en;q=0.8\r\n" \
-"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n" \
-"Cookie: name=wookie\r\n" \
-"\r\n"
-
-#define ITERATIONS 10000000
-
-double now_seconds(void)
-{
- struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts);
- return ts.tv_sec + ts.tv_nsec / 1e9;
-}
-
-int main(void) {
- char* raw = REQ;
- size_t raw_len = strlen(raw);
- int i;
-
- double t0 = now_seconds();
- for (i = 0; i < ITERATIONS; ++i) {
- httpp_req_t* req = httpp_parse_request(raw);
- if (!req) {
- fprintf(stderr, "parse failed at iter %d\n", i);
- return 1;
- }
-
- httpp_req_free(req);
- }
- double t1 = now_seconds();
-
- printf("Elapsed %f seconds.\n", t1 - t0);
- return 0;
-}
diff --git a/elapsed.svg b/elapsed.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink" width="600" height="390" style="overflow: hidden;" aria-label="A chart."><defs id="_ABSTRACT_RENDERER_ID_534"><clipPath id="_ABSTRACT_RENDERER_ID_535"><rect x="115" y="75" width="371" height="241"></rect></clipPath><filter id="_ABSTRACT_RENDERER_ID_536"><feGaussianBlur in="SourceAlpha" stdDeviation="2"></feGaussianBlur><feOffset dx="1" dy="1"></feOffset><feComponentTransfer><feFuncA type="linear" slope="0.1"></feFuncA></feComponentTransfer><feMerge><feMergeNode></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter></defs><rect x="0" y="0" width="600" height="390" stroke="none" stroke-width="0" fill="#ffffff"></rect><g><text text-anchor="start" x="115" y="52.05" font-family="Arial" font-size="13" font-weight="bold" stroke="none" stroke-width="0" fill="#000000">Elapsed time (10 000 000 iterations)</text><rect x="115" y="41" width="371" height="13" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect></g><g><rect x="115" y="75" width="371" height="241" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect><g clip-path="url(https://www.rapidtables.com/tools/bar-graph.html#_ABSTRACT_RENDERER_ID_535)"><g><rect x="115" y="315" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="213" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="162" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="126" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="98" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="75" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="226" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="201" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="190" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="180" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="171" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="154" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="146" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="139" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="132" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="120" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="114" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="108" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="103" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="93" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="88" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="84" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="79" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect></g><g><rect x="139" y="91" width="76" height="224" stroke="none" stroke-width="0" fill="#3584e4"></rect><rect x="262" y="161" width="77" height="154" stroke="none" stroke-width="0" fill="#3584e4"></rect><rect x="386" y="214" width="76" height="101" stroke="none" stroke-width="0" fill="#3584e4"></rect></g><g><rect x="115" y="315" width="371" height="1" stroke="none" stroke-width="0" fill="#333333"></rect></g></g><g></g><g><g><text text-anchor="middle" x="177.16666666666666" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">http-parser</text></g><g><text text-anchor="middle" x="300.5" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">httpp</text></g><g><text text-anchor="middle" x="423.8333333333333" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">picohttpparser</text></g><g><text text-anchor="end" x="102" y="319.55" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">0</text></g><g><text text-anchor="end" x="102" y="218.23350000000002" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">2</text></g><g><text text-anchor="end" x="102" y="167.2341" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">3</text></g><g><text text-anchor="end" x="102" y="131.0494" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">4</text></g><g><text text-anchor="end" x="102" y="102.9824" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">5</text></g><g><text text-anchor="end" x="102" y="80.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">6</text></g></g></g><g><g><text text-anchor="middle" x="300.5" y="368.05" font-family="Arial" font-size="13" font-style="italic" stroke="none" stroke-width="0" fill="#222222">Parsers</text><rect x="115" y="357" width="371" height="13" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect></g><g><text text-anchor="middle" x="52.05" y="195.5" font-family="Arial" font-size="13" font-style="italic" transform="rotate(-90 52.05 195.5)" stroke="none" stroke-width="0" fill="#222222">Seconds</text><path d="M40.99999999999999,316L41.00000000000001,75L54.00000000000001,75L53.99999999999999,316Z" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></path></g></g><g></g></svg>
+\ No newline at end of file
diff --git a/example.c b/example.c
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define HTTPP_IMPLEMENTATION
+#include "httpp.h"
+
+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}";
+
+int main()
+{
+ httpp_req_t* parsed = httpp_parse_request(req);
+ printf("%s\n", parsed->body); // parsed, malloc'd body
+ printf("%i\n", parsed->method); // Method is an enum!
+
+ const char* method = httpp_method_to_string(parsed->method); // Method as string (e.g POST)
+ printf("%s\n", method);
+
+ httpp_header_t* host = httpp_find_header(parsed, "Host");
+ printf("%s\n", host->value); // api.example.com
+
+ httpp_res_t* response = httpp_res_new();
+ response->code = Ok; // or 200
+ httpp_add_header(response, "Host", "somehost.some.where");
+ httpp_add_header(response, "Status", "ok");
+
+ httpp_res_set_body(response, "Some body");
+ char* raw = httpp_res_to_raw(response); // Convert to a malloc'd raw string
+
+ printf("\nComposed response: \n");
+ printf("----\n%s\n----\n", raw); // Or write it to a socket
+
+ // Don't forget to free everything
+ httpp_req_free(parsed); // Free parsed request
+ httpp_res_free(response); // Free response structure
+ free(raw); // Free composed raw response
+}
+\ No newline at end of file
diff --git a/httpp.h b/httpp.h
@@ -4,8 +4,7 @@
* Pipelined requests are not supported because nobody realy cares about them
* (https://en.wikipedia.org/wiki/HTTP_pipelining#Implementation_status)
*
- * Chunked transfer is not really supported
- *
+ * TODO Chunked transfer is not really supported
* TODO Folded headers aren't handled.
*/
@@ -98,18 +97,26 @@ const char* httpp_status_to_string(httpp_status_t s);
httpp_req_t* httpp_req_new();
void httpp_req_free(httpp_req_t* req);
-httpp_req_t* httpp_parse_request(char* raw);
+httpp_req_t* httpp_parse_request(char* raw);
httpp_header_t* httpp_parse_header(httpp_headers_arr_t* hs, char* line, size_t content_len);
httpp_headers_arr_t* httpp_headers_arr_new();
+httpp_header_t* httpp_headers_arr_append(httpp_headers_arr_t* hs, httpp_header_t header);
+httpp_header_t* httpp_headers_arr_add(httpp_headers_arr_t* hs, char* name, char* value); // stdrup and httpp_headers_arr_append
+httpp_header_t* httpp_headers_arr_find(httpp_headers_arr_t* hs, char* name);
void httpp_headers_arr_free(httpp_headers_arr_t* hs);
-httpp_header_t* httpp_headers_append(httpp_headers_arr_t* hs, httpp_header_t header);
-httpp_header_t* httpp_headers_add(httpp_headers_arr_t* hs, char* name, char* value); // stdrup and httpp_headers_append
httpp_res_t* httpp_res_new();
-void httpp_res_free(httpp_res_t* res);
+int httpp_res_set_body(httpp_res_t* res, char* body); // strdup body and set it
char* httpp_res_to_raw(httpp_res_t* res);
+void httpp_res_free(httpp_res_t* res);
+
+#define httpp_add_header(req_or_res, name, value) \
+ (httpp_headers_arr_add((req_or_res)->headers, name, value))
+
+#define httpp_find_header(req_or_res, name) \
+ (httpp_headers_arr_find((req_or_res)->headers, name))
#ifdef HTTPP_IMPLEMENTATION
@@ -209,7 +216,6 @@ const char* httpp_status_to_string(httpp_status_t s)
}
}
-
httpp_headers_arr_t* httpp_headers_arr_new()
{
httpp_headers_arr_t* out = (httpp_headers_arr_t*) malloc(sizeof(httpp_headers_arr_t));
@@ -267,7 +273,7 @@ void httpp_req_free(httpp_req_t* req)
free(req);
}
-httpp_header_t* httpp_headers_append(httpp_headers_arr_t* hs, httpp_header_t header)
+httpp_header_t* httpp_headers_arr_append(httpp_headers_arr_t* hs, httpp_header_t header)
{
if (hs->length >= hs->capacity) {
size_t new_cap = hs->capacity * 2;
@@ -293,7 +299,19 @@ httpp_header_t* httpp_headers_append(httpp_headers_arr_t* hs, httpp_header_t hea
return &hs->arr[hs->length - 1];
}
-httpp_header_t* httpp_headers_add(httpp_headers_arr_t* hs, char* name, char* value)
+httpp_header_t* httpp_headers_arr_find(httpp_headers_arr_t* hs, char* name)
+{
+ // For the sake of simplicity and minimalism, it's just a for loop. No hash table here.
+ for (size_t i = 0; i < hs->length; i++) {
+ if (strcmp(hs->arr[i].name, name) == 0)
+ return &hs->arr[i];
+ }
+
+ return NULL;
+}
+
+
+httpp_header_t* httpp_headers_arr_add(httpp_headers_arr_t* hs, char* name, char* value)
{
if (!name || !value)
return NULL;
@@ -303,7 +321,7 @@ httpp_header_t* httpp_headers_add(httpp_headers_arr_t* hs, char* name, char* val
strdup(value)
};
- httpp_header_t* out = httpp_headers_append(hs, h);
+ httpp_header_t* out = httpp_headers_arr_append(hs, h);
if (out == NULL) {
free(h.name);
@@ -346,7 +364,7 @@ httpp_header_t* httpp_parse_header(httpp_headers_arr_t* hs, char* line, size_t c
memcpy(value, value_start, value_len);
value[value_len] = '\0';
- return httpp_headers_append(hs, (httpp_header_t){name, value});
+ return httpp_headers_arr_append(hs, (httpp_header_t){name, value});
}
static int parse_start_line(char** itr, httpp_req_t* dest)
@@ -438,12 +456,23 @@ httpp_res_t* httpp_res_new()
return out;
}
+int httpp_res_set_body(httpp_res_t* res, char* body)
+{
+ char* content = strdup(body);
+ if (!content)
+ return 1;
+
+ res->body = content;
+ return 0;
+}
+
void httpp_res_free(httpp_res_t* res)
{
if (!res)
return;
httpp_headers_arr_free(res->headers);
+ free(res->body);
}
char* httpp_res_to_raw(httpp_res_t* res)
diff --git a/requests.svg b/requests.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink" width="600" height="390" style="overflow: hidden;" aria-label="A chart."><defs id="_ABSTRACT_RENDERER_ID_974"><clipPath id="_ABSTRACT_RENDERER_ID_975"><rect x="115" y="75" width="371" height="241"></rect></clipPath><filter id="_ABSTRACT_RENDERER_ID_976"><feGaussianBlur in="SourceAlpha" stdDeviation="2"></feGaussianBlur><feOffset dx="1" dy="1"></feOffset><feComponentTransfer><feFuncA type="linear" slope="0.1"></feFuncA></feComponentTransfer><feMerge><feMergeNode></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter></defs><rect x="0" y="0" width="600" height="390" stroke="none" stroke-width="0" fill="#ffffff"></rect><g><text text-anchor="start" x="115" y="52.05" font-family="Arial" font-size="13" font-weight="bold" stroke="none" stroke-width="0" fill="#000000">Parsed requests per second</text><rect x="115" y="41" width="371" height="13" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect></g><g><rect x="115" y="75" width="371" height="241" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect><g clip-path="url(https://www.rapidtables.com/tools/bar-graph.html#_ABSTRACT_RENDERER_ID_975)"><g><rect x="115" y="315" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="198" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="143" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="105" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="75" width="371" height="1" stroke="none" stroke-width="0" fill="#cccccc"></rect><rect x="115" y="219" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="212" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="204" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="191" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="185" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="179" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="173" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="168" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="162" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="157" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="153" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="148" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="139" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="135" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="131" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="127" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="123" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="119" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="115" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="112" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="108" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="102" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="98" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="95" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="92" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="89" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="86" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="83" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="80" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect><rect x="115" y="78" width="371" height="1" stroke="none" stroke-width="0" fill="#ebebeb"></rect></g><g><rect x="139" y="207" width="76" height="108" stroke="none" stroke-width="0" fill="#3584e4"></rect><rect x="262" y="133" width="77" height="182" stroke="none" stroke-width="0" fill="#3584e4"></rect><rect x="386" y="76" width="76" height="239" stroke="none" stroke-width="0" fill="#3584e4"></rect></g><g><rect x="115" y="315" width="371" height="1" stroke="none" stroke-width="0" fill="#333333"></rect></g></g><g></g><g><g><text text-anchor="middle" x="177.16666666666666" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">http-parser</text></g><g><text text-anchor="middle" x="300.5" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">httpp</text></g><g><text text-anchor="middle" x="423.8333333333333" y="335.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#222222">picohttpparser</text></g><g><text text-anchor="end" x="102" y="319.55" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">0</text></g><g><text text-anchor="end" x="102" y="202.5873" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">2,000,000</text></g><g><text text-anchor="end" x="102" y="148.36370000000002" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">3,000,000</text></g><g><text text-anchor="end" x="102" y="109.89139999999999" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">4,000,000</text></g><g><text text-anchor="end" x="102" y="80.05" font-family="Arial" font-size="13" stroke="none" stroke-width="0" fill="#444444">5,000,000</text></g></g></g><g><g><text text-anchor="middle" x="300.5" y="368.05" font-family="Arial" font-size="13" font-style="italic" stroke="none" stroke-width="0" fill="#222222">Parsers</text><rect x="115" y="357" width="371" height="13" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></rect></g><g><text text-anchor="middle" x="26.55" y="195.5" font-family="Arial" font-size="13" font-style="italic" transform="rotate(-90 26.55 195.5)" stroke="none" stroke-width="0" fill="#222222">Requests</text><path d="M15.499999999999991,316L15.500000000000005,75L28.500000000000007,75L28.499999999999993,316Z" stroke="none" stroke-width="0" fill-opacity="0" fill="#ffffff"></path></g></g><g></g></svg>
+\ No newline at end of file
diff --git a/test.c b/test.c
@@ -73,11 +73,11 @@ int main()
httpp_res_t* res = httpp_res_new();
res->code = 200;
- httpp_headers_add(res->headers, "Host", "idk.me.com");
- httpp_headers_add(res->headers, "Home", "pkeofkwekgfwktokwt9wt293430592304");
- httpp_headers_add(res->headers, "SOmethin", "afkofkeokfoekfo");
- char* body = "{\"hello\": 123}\n";
- res->body = body;
+ httpp_headers_arr_add(res->headers, "Host", "idk.me.com");
+ httpp_headers_arr_add(res->headers, "Home", "pkeofkwekgfwktokwt9wt293430592304");
+ httpp_headers_arr_add(res->headers, "SOmethin", "afkofkeokfoekfo");
+
+ httpp_res_set_body(res, "{\"hello\": 123}\n");
char* raw = httpp_res_to_raw(res);
printf("-----------\n");