diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2023-09-24 10:07:23 +0200 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2023-09-24 10:07:23 +0200 |
commit | 9a242bce57dfc9982328485f7ab19e2f8ccb0fcc (patch) | |
tree | c3e9a15b5443f493bad449e985168d598c76d099 | |
parent | a626c9b2c754af978bfb16e541d5a5f7302466f5 (diff) | |
download | kit-9a242bce57dfc9982328485f7ab19e2f8ccb0fcc.zip |
HTTP1 work in progress
-rw-r--r-- | source/kit/http1.h | 245 | ||||
-rw-r--r-- | source/kit/sockets.h | 22 |
2 files changed, 252 insertions, 15 deletions
diff --git a/source/kit/http1.h b/source/kit/http1.h new file mode 100644 index 0000000..3158bd6 --- /dev/null +++ b/source/kit/http1.h @@ -0,0 +1,245 @@ +#ifndef KIT_HTTP1_H +#define KIT_HTTP1_H + +#include "types.h" +#include "string_builder.h" +#include "sockets.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// FIXME +// Rewrite in C. +// + +#define KIT_HTTP1_CONTENT_TYPE \ + "Content-Type: text/plain; version=0.0.4; charset=utf-8"; +#define KIT_HTTP1_NEWLINE "\r\n" +#define KIT_HTTP1_SPACE " " +#define KIT_HTTP1_HEADER_SEPARATOR ": " + +enum { + KIT_HTTP1_OPTIONS, + KIT_HTTP1_GET, + KIT_HTTP1_HEAD, + KIT_HTTP1_POST, + KIT_HTTP1_PUT, + KIT_HTTP1_DELETE, + KIT_HTTP1_TRACE, + KIT_HTTP1_CONNECT, +}; + +typedef struct { + kit_str_builder_t key; + kit_str_builder_t value; +} kit_http1_str_pair_t; + +typedef KIT_DA(kit_http1_str_pair_t) kit_http1_str_map_t; + +typedef struct { + kit_str_builder_t str; + i64 position; +} kit_http1_tok_t; + +typedef struct { + kit_str_builder_t protocol; + kit_str_builder_t host; + kit_str_builder_t port; + kit_str_builder_t address; + kit_str_builder_t query_string; + kit_str_builder_t hash; + kit_http1_str_map_t parameters; +} kit_http1_uri_t; + +typedef struct { + i8 success; + kit_str_builder_t protocol; + kit_str_builder_t response; + kit_str_builder_t response_str; + kit_http1_str_map_t header; + kit_str_builder_t body; +} kit_http1_response_t; + +static kit_str_builder_t kit_http1_tok_next(kit_http1_tok_t *tok, + kit_str_t search, + i8 return_tail) { + i64 hit = str.find(search, position); + if (hit == string::npos) { + if (return_tail) { + return tail(); + } else { + return ""; + } + } + + i64 previous_position = position; + position = hit + search.length(); + + return str.substr(previous_position, hit - previous_position); +} + +static kit_str_builder_t kit_http1_tok_tail(kit_http1_tok_t *tok) { + i64 previous_position = position; + position = str.length(); + return str.substr(previous_position); +} + +static void kit_http1_uri_parse_params(kit_http1_uri_t *uri) { + kit_http1_tok_t qt(query_string); + for (;;) { + kit_str_builder_t key = qt.next("=", 0); + if (key == "") + break; + parameters[key] = qt.next("&", 1); + } +} + +static void kit_http1_uri_init(kit_http1_uri_t *uri, + kit_str_builder_t input, + i8 should_use_params) { + kit_http1_tok_t t = kit_http1_tok_t(input); + protocol = t.next("://", 0); + kit_str_builder_t host_port_str = t.next("/", 0); + + kit_http1_tok_t host_port(host_port_str); + + host = host_port.next(host_port_str[0] == '[' ? "]:" : ":", 1); + + if (host[0] == '[') + host = host.substr(1, host.size() - 1); + + port = host_port.tail(); + if (port.empty()) + port = "80"; + + address = t.next("?", 1); + query_string = t.next("#", 1); + + hash = t.tail(); + + if (should_use_params) { + kit_http1_uri_parse_params(); + } +} + +static str_t kit_http1_method_to_str(i32 method) { + static str_t methods[] = { SZ("OPTIONS"), SZ("GET"), + SZ("HEAD"), SZ("POST"), + SZ("PUT"), SZ("DELETE"), + SZ("TRACE"), SZ("CONNECT") }; + return methods[method]; +} + +static socket_t kit_http1_connect_to_uri(kit_http1_uri_t *uri) { + struct addrinfo hints, *result, *rp; + + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + i32 getaddrinfo_result = getaddrinfo( + uri.host.c_str(), uri.port.c_str(), &hints, &result); + + if (getaddrinfo_result != 0) + return INVALID_SOCKET; + + socket_t fd = INVALID_SOCKET; + + for (rp = result; rp != nullptr; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (fd == INVALID_SOCKET) + continue; + + i32 connect_result = connect(fd, rp->ai_addr, + (socklen_t) rp->ai_addrlen); + + if (connect_result == -1) { + closesocket(fd); + fd = INVALID_SOCKET; + continue; + } + + break; + } + + freeaddrinfo(result); + + return fd; +} + +static kit_str_builder_t kit_http1_buffered_read(socket_t fd) { + i64 initial_factor = 4, buffer_increment_size = 8192, + buffer_size = 0, bytes_read = 0; + kit_str_builder_t buffer; + + buffer.resize(initial_factor * buffer_increment_size); + + bytes_read = recv( + fd, ((char *) buffer.c_str()) + buffer_size, + static_cast<socklen_t>(buffer.size() - buffer_size), 0); + + buffer_size += bytes_read; + + buffer.resize(buffer_size); + return buffer; +} + +static kit_http1_response_t kit_http1_request( + i32 method, kit_http1_uri_t *uri, kit_str_builder_t *body) { + + socket_t fd = kit_http1_connect_to_uri(uri); + + if (fd == INVALID_SOCKET) { + return { .success = 0 }; + } + + kit_str_builder_t request = + string(kit_http1_method_to_str(method)) + string(" /") + + uri.address + ((uri.query_string == "") ? "" : "?") + + uri.query_string + " HTTP/1.1" + KIT_HTTP1_NEWLINE + + "Host: " + uri.host + ":" + uri.port + KIT_HTTP1_NEWLINE + + "Accept: */*" + KIT_HTTP1_NEWLINE + content_type + + KIT_HTTP1_NEWLINE + + "Content-Length: " + to_string(body.size()) + + KIT_HTTP1_NEWLINE + KIT_HTTP1_NEWLINE + body; + + send(fd, request.c_str(), static_cast<socklen_t>(request.size()), + 0); + + kit_str_builder_t buffer = kit_http1_buffered_read(fd); + + closesocket(fd); + + kit_http1_response_t result = { .success = 1 }; + + kit_http1_tok_t bt(buffer); + + result.protocol = bt.next(KIT_HTTP1_SPACE, 0); + result.response = bt.next(KIT_HTTP1_SPACE, 0); + result.response_str = bt.next(KIT_HTTP1_NEWLINE, 0); + + kit_str_builder_t header = bt.next( + KIT_HTTP1_NEWLINE KIT_HTTP1_NEWLINE, 0); + + result.body = bt.tail(); + + kit_http1_tok_t ht(header); + + do { + kit_str_builder_t key = ht.next(KIT_HTTP1_HEADER_SEPARATOR, 0); + if (key.size == 0) + break; + result.header[key] = ht.next(KIT_HTTP1_NEWLINE, 1); + } while (1); + + return result; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/kit/sockets.h b/source/kit/sockets.h index 2c37fcb..31253fc 100644 --- a/source/kit/sockets.h +++ b/source/kit/sockets.h @@ -10,6 +10,10 @@ # pragma GCC diagnostic ignored "-Wunknown-pragmas" # endif +# ifdef __cplusplus +extern "C" { +# endif + # if defined(_WIN32) && !defined(__CYGWIN__) # define WIN32_LEAN_AND_MEAN @@ -19,10 +23,6 @@ # define socket_t SOCKET # define socklen_t i32 -# ifdef __cplusplus -extern "C" { -# endif - static kit_status_t kit_sockets_init(void) { WSADATA data; memset(&data, 0, sizeof data); @@ -51,10 +51,6 @@ static i32 kit_socket_set_nonblocking(socket_t s) { : KIT_ERROR_SOCKET_CONTROL_FAILED; } -# ifdef __cplusplus -} -# endif - # else # include <arpa/inet.h> @@ -72,10 +68,6 @@ static i32 kit_socket_set_nonblocking(socket_t s) { # define closesocket close # define INVALID_SOCKET -1 -# ifdef __cplusplus -extern "C" { -# endif - static kit_status_t kit_sockets_init(void) { signal(SIGPIPE, SIG_IGN); return KIT_OK; @@ -99,10 +91,10 @@ static i32 kit_socket_set_nonblocking(socket_t s) { : KIT_ERROR_SOCKET_CONTROL_FAILED; } -# ifdef __cplusplus -} -# endif +# endif +# ifdef __cplusplus +} # endif # ifdef __GNUC__ |