Skip to content

Commit

Permalink
Merge pull request #1 from ricksterhd123/http-parser
Browse files Browse the repository at this point in the history
http parsing and response generation
  • Loading branch information
ricksterhd123 authored Jan 31, 2024
2 parents 7e5c687 + 49dbd05 commit 02579dc
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 44 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"twxs.cmake",
"Shopify.ruby-extensions-pack",
"castwide.solargraph",
"rubocop.vscode-rubocop"
"rubocop.vscode-rubocop",
"github.vscode-github-actions"
]
}
}
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"rubocop.mode": "onlyRunGlobally",
"files.associations": {
"picohttpparser.h": "c"
"picohttpparser.h": "c",
"type_traits": "c",
"cmath": "c",
"limits": "c",
"cstdlib": "cpp"
}
}
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)

project(mruv VERSION 1.0)

add_executable(mruv src/main.c)
add_executable(mruv src/main.c src/http1.h)
configure_file(config.rb config.rb COPYONLY)

add_library(picohttpparser STATIC "${PROJECT_SOURCE_DIR}/lib/picohttpparser/picohttpparser.c")
Expand All @@ -18,4 +18,4 @@ set_target_properties(libuv PROPERTIES INTERFACE_INCLUDE_DIRECTORIES /usr/local/

target_compile_options(mruv PUBLIC "-pthread")
target_link_libraries(mruv PRIVATE libuv mruby m dl pthread picohttpparser)
target_include_directories(mruv PRIVATE "${PROJECT_BINARY_DIR}")
target_include_directories(mruv PRIVATE "${PROJECT_SOURCE_DIR}")
18 changes: 2 additions & 16 deletions config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,6 @@
##
# mruv socket handler function fizzbuzz example
#
def handler(event, context)
puts event, context
n = event[:body].chomp.to_i
if n.positive? && (n < 100)
if (n % 15).zero?
'fizzbuzz'
elsif (n % 3).zero?
'fizz'
elsif (n % 5).zero?
'buzz'
else
env
end
else
'Must be number between 1 and 99'
end
def handler
'<h1>Hello world!</h1>'
end
267 changes: 267 additions & 0 deletions src/http1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// Simple HTTP/1.1 library
// - Uses picohttpparser for parsing HTTP request/responses
// - Uses my own code for writing HTTP request/responses

#pragma once

#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <picohttpparser.h>

#include "utils.h"

#define MAX_HTTP_HEADERS 100
#define HTTP_MINOR_VERSION 1

typedef struct
{
char *name;
size_t name_len;
char *value;
size_t value_len;
} http_header_t;

http_header_t *get_n_http_header(const char *name, size_t name_len, const char *value, size_t value_len)
{

// if (name[name_len] != '\0' || value[value_len] != '\0') {
// fprintf(stderr, "FATAL2: Copied non null-terminated string - %s %s\n", name, value);
// return NULL;
// }

http_header_t *header = (http_header_t *)malloc(sizeof(http_header_t));
if (header == NULL)
{
fprintf(stderr, "FATAL: Failed to allocate memory\n");
return NULL;
}

header->name = (char *)malloc(sizeof(char) * name_len);
header->name_len = name_len;
header->value = (char *)malloc(sizeof(char) * value_len);
header->value_len = value_len;

memcpy(header->name, name, name_len);
memcpy(header->value, value, value_len);

return header;
}

http_header_t *get_http_header(const char *name, const char *value)
{
return get_n_http_header(name, strlen(name) + 1, value, strlen(value) + 1);
}

void free_http_header(http_header_t *header)
{
free(header->name);
free(header->value);
}

char *get_status_reason_code(unsigned short status_code)
{
char status_code_str[4];
itoa(status_code, status_code_str);

char *reason;

if (status_code == 200)
{
reason = "OK";
}
else if (status_code == 400)
{
reason = "Bad Request";
}
else
{
reason = "Unknown";
}

size_t status_reason_code_len = strlen(status_code_str) + strlen(reason) + 2;
char *status_reason_code = (char *)malloc(sizeof(char) * status_reason_code_len);
snprintf(status_reason_code, status_reason_code_len, "%s %s", status_code_str, reason);

return status_reason_code;
}

typedef struct
{
size_t num_headers;
struct phr_header headers[MAX_HTTP_HEADERS];
const char *method;
size_t method_len;
const char *path;
size_t path_len;
} http_request_t;

typedef struct
{
unsigned int status_code;

char *http_date;
size_t http_date_len;

char *http_version;
size_t http_version_len;

char *status_reason;
size_t status_reason_len;

http_header_t **headers;
size_t num_headers;

char *body;
size_t body_len;
} http_response_t;

http_request_t *parse_http_request(const char *buf, size_t len)
{
int minor_version;

http_request_t *req = (http_request_t *)malloc(sizeof(http_request_t));
int pret = phr_parse_request(buf, len, &req->method, &req->method_len, &req->path, &req->path_len, &minor_version, req->headers, &req->num_headers, 0);

if (pret == -1)
{
free(req);
return NULL;
}

return req;
}

http_response_t *generate_http_response(unsigned int status_code, const char *body, size_t body_len)
{
http_response_t *response = (http_response_t *)malloc(sizeof(http_response_t));

if (response == NULL)
{
return NULL;
}

time_t result = time(NULL);
response->http_version = new_str("HTTP/1.1");
response->http_version_len = strlen(response->http_version) + 1;

response->http_date = new_str(asctime(gmtime(&result)));
response->http_date_len = strlen(response->http_date) + 1;

// Remove the \n from the end of the string
// \n\0
response->http_date[response->http_date_len - 2] = '\0';

response->status_code = status_code;
response->status_reason = get_status_reason_code(status_code);
response->status_reason_len = strlen(response->status_reason) + 1;

response->body_len = body_len;
response->body = (char *)malloc(sizeof(char) * body_len);
memcpy(response->body, body, body_len);

// Calculate Content-Length header
size_t content_length_str_len = ceil(log10(body_len)) + 1;
char *content_length_str = (char *)malloc(sizeof(char) * content_length_str_len);
itoa(strlen(body), content_length_str);

http_header_t *content_length = get_http_header("Content-Length", content_length_str);
http_header_t *date = get_http_header("Date", response->http_date);
http_header_t *server = get_http_header("Server", "mruv");
http_header_t *last_modified = get_http_header("Last-Modified", response->http_date);
http_header_t *accept_ranges = get_http_header("Accept-Ranges", "none");
http_header_t *vary = get_http_header("Vary", "Accept-Encoding");
http_header_t *connection = get_http_header("Connection", "Closed");
http_header_t *content_type = get_http_header("Content-Type", "text/html");

response->headers = (http_header_t **)malloc(sizeof(http_header_t *) * 100);
response->headers[0] = date;
response->headers[1] = server;
response->headers[2] = last_modified;
response->headers[3] = accept_ranges;
response->headers[4] = content_length;
response->headers[5] = vary;
response->headers[6] = connection;
response->headers[7] = content_type;
response->num_headers = 8;

free(content_length_str);

return response;
}

void free_http_response(http_response_t *response)
{
for (size_t i = 0; i < response->num_headers; i++)
{
free_http_header(response->headers[i]);
}

if (response->body != NULL)
{
free(response->body);
}

free(response->http_date);
free(response->http_version);
free(response->status_reason);

free(response);
}

char *http_response_to_str(http_response_t *response)
{
// Calculate Status line length first
// [version_length] [status_reason]\r\n\0
size_t status_line_len = response->http_version_len + response->status_reason_len;
char* status_line = (char*) calloc(status_line_len, sizeof(char));
snprintf(status_line, status_line_len, "%s %s", response->http_version, response->status_reason);

// Pre-calulate total header string length, allocate and build string
size_t headers_str_len = 0;
for (size_t i = 0; i < response->num_headers; i++) {
headers_str_len += response->headers[i]->name_len + 1 + response->headers[i]->value_len + 2;
}

char* headers_str = (char*) calloc(headers_str_len, sizeof(char));
char* headers_str_tmp = headers_str;

for (size_t i = 0; i < response->num_headers; i++) {
if (response->headers[i] == NULL) {
continue;
}
size_t headers_str_line_len = response->headers[i]->name_len + 1 + response->headers[i]->value_len + 2;
char* headers_str_line = (char*) calloc(headers_str_line_len, sizeof(char));
snprintf(headers_str_line, headers_str_line_len, "%s: %s\r\n", response->headers[i]->name, response->headers[i]->value);
// printf("%s: %s\r\n", response->headers[i]->name, response->headers[i]->value);
strncat(headers_str, headers_str_line, headers_str_line_len);
free(headers_str_line);
}

// Add [status_line]\r\n[header...]\r\n[header...]\r\n[header...]\r\n[body]\0 terminated character
size_t total_response_len = status_line_len + 2 + headers_str_len + 2 + response->body_len;

char* response_str = (char*) calloc(total_response_len, sizeof(char));
snprintf(response_str, total_response_len, "%s\r\n%s\r\n%s", status_line, headers_str, response->body);

free(headers_str);
free(status_line);

return response_str;
}

/**
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Accept-Ranges: none
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
Hello World! My content includes a trailing CRLF.
*/
Loading

0 comments on commit 02579dc

Please sign in to comment.