summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2023-09-24 10:07:23 +0200
committerMitya Selivanov <automainint@guattari.tech>2023-09-24 10:07:23 +0200
commit9a242bce57dfc9982328485f7ab19e2f8ccb0fcc (patch)
treec3e9a15b5443f493bad449e985168d598c76d099
parenta626c9b2c754af978bfb16e541d5a5f7302466f5 (diff)
downloadkit-9a242bce57dfc9982328485f7ab19e2f8ccb0fcc.zip
HTTP1 work in progress
-rw-r--r--source/kit/http1.h245
-rw-r--r--source/kit/sockets.h22
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__