Skip to content

Commit

Permalink
Add HTTP response rendering functionality
Browse files Browse the repository at this point in the history
Introduced `render_http_response` function to build HTTP responses, including status-line processing. Added new status codes and a test for HTTP 200 response rendering without a body.
  • Loading branch information
SeriousSamV committed Oct 31, 2024
1 parent f063ae1 commit 9fdb7de
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 15 deletions.
102 changes: 87 additions & 15 deletions src/tiny_http/tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

enum parse_http_request_status {
PARSE_OK = 0,
E_REQ_IS_NULL = -11,
E_MALFORMED_HTTP_HEADER = 1,
E_ALLOC_MEM_FOR_HEADERS = 2,
E_HTTP_METHOD_NOT_SUPPORTED = 3,
E_MALFORMED_HTTP_REQUEST_LINE = 4,
E_HTTP_VERSION_NOT_SUPPORTED = 5,
PARSE_E_REQ_IS_NULL = -11,
PARSE_E_MALFORMED_HTTP_HEADER = 1,
PARSE_E_ALLOC_MEM_FOR_HEADERS = 2,
PARSE_E_HTTP_METHOD_NOT_SUPPORTED = 3,
PARSE_E_MALFORMED_HTTP_REQUEST_LINE = 4,
PARSE_E_HTTP_VERSION_NOT_SUPPORTED = 5,
};


Expand Down Expand Up @@ -48,6 +48,78 @@ void destroy_http_request(http_request *http_request) {
free(http_request);
}

enum render_http_response_status render_http_response(
const http_response *http_response,
uint8_t **out_response_octets,
size_t *out_response_len) {
if (http_response == nullptr) {
fprintf(stderr, "http_response is already null");
fflush(stderr);
*out_response_len = 0;
return RENDER_E_RESPONSE_OBJ_IS_NULL;
}
if (out_response_len == nullptr) {
fprintf(stderr, "out_response_len is null");
fflush(stderr);
return RENDER_E_OUT_PARAM_ADDR_IS_NULL;
}
*out_response_octets = calloc(
// TODO: have a way to predict the size more accurately to avoid over or under allocation
http_response->body_len + http_response->headers_cnt * 2000 + 32,
sizeof(uint8_t));
if (*out_response_octets == nullptr) {
fprintf(stderr, "cannot alloc mem for out_response_octets");
fflush(stderr);
*out_response_len = 0;
return RENDER_E_MEM_ALLOC_FAILED;
}

size_t octets_written = 0;

// region status line
// Status-Line:
// "HTTP/" 1*DIGIT "." 1*DIGIT SP 3DIGIT SP *<TEXT, excluding CR, LF>
// "HTTP/<http_version><SP><http response status><SP><reason phrase><CR><LF>"

// region "HTTP/x.y" part
strcpy((char *) *out_response_octets, "HTTP/"); // "HTTP/"
octets_written += 5;
if (http_response->version == HTTP_1_0) {
strcpy((char *) *out_response_octets + octets_written, "1.0");
octets_written += 3;
} else {
fprintf(stderr, "unsupported HTTP version");
fflush(stderr);
*out_response_len = 0;
free(*out_response_octets);
*out_response_octets = nullptr;
return RENDER_E_HTTP_VERSION_NOT_SUPPORTED;
}
// endregion "HTTP/x.y" part
strcpy((char *) *out_response_octets + octets_written, " ");
octets_written += 1;
// region http status
snprintf((char *) *out_response_octets + octets_written, 5, "%d ", http_response->status_code);
octets_written += 4;
// endregion http status
// region reason phrase
const size_t reason_phrase_len = strnlen((char *) http_response->reason_phrase, 128);
strncpy((char *) *out_response_octets + octets_written,
(const char *) http_response->reason_phrase,
reason_phrase_len);
octets_written += reason_phrase_len;
// endregion reason phrase
strncpy((char *) *out_response_octets + octets_written, "\r\n", 2);
octets_written += 2;
// endregion status line

if (http_response->headers != nullptr && http_response->headers_cnt > 0) {
}

*out_response_len = octets_written;
return RENDER_OK;
}

/**
*
* @param headers http_headers from which we've to get the Content-Length
Expand Down Expand Up @@ -84,7 +156,7 @@ enum parse_http_request_status parse_http_request_body(
if (request == nullptr) {
fprintf(stderr, "Error: null request\n");
fflush(stderr);
return E_REQ_IS_NULL;
return PARSE_E_REQ_IS_NULL;
}
if (*ptr < http_packet_len) {
const ssize_t body_len_from_header = request != nullptr && request->headers != nullptr
Expand All @@ -107,7 +179,7 @@ enum parse_http_request_status parse_http_request_headers(
const size_t http_packet_len,
http_request *request,
size_t *ptr) {
if (request == nullptr) return E_REQ_IS_NULL;
if (request == nullptr) return PARSE_E_REQ_IS_NULL;
for (size_t i = 0; *ptr < http_packet_len; i++) {
if ((*ptr >= http_packet_len || *ptr + 1 >= http_packet_len)
|| (http_packet[(*ptr)] == '\r'
Expand All @@ -126,7 +198,7 @@ enum parse_http_request_status parse_http_request_headers(
if (header_name_len == 0) {
fprintf(stderr, "malformed header");
fflush(stderr);
return E_MALFORMED_HTTP_HEADER;
return PARSE_E_MALFORMED_HTTP_HEADER;
}
header->name = strndup((char *) http_packet + *ptr - header_name_len, header_name_len - 1);

Expand All @@ -150,14 +222,14 @@ enum parse_http_request_status parse_http_request_headers(
if (request->headers == nullptr) {
fprintf(stderr, "cannot allocate memory for new headers");
fflush(stderr);
return E_ALLOC_MEM_FOR_HEADERS;
return PARSE_E_ALLOC_MEM_FOR_HEADERS;
}
} else {
http_header **new_headers = realloc(request->headers, sizeof(http_header *) * (i + 1));
if (new_headers == nullptr) {
fprintf(stderr, "cannot allocate memory for new headers");
fflush(stderr);
return E_ALLOC_MEM_FOR_HEADERS;
return PARSE_E_ALLOC_MEM_FOR_HEADERS;
}
request->headers = new_headers;
}
Expand All @@ -174,7 +246,7 @@ enum parse_http_request_status parse_http_request_line_from_packet(
const size_t http_packet_len,
http_request *request,
size_t *ptr) {
if (request == nullptr) return E_REQ_IS_NULL;
if (request == nullptr) return PARSE_E_REQ_IS_NULL;
size_t start_uri = 0;
if (strncmp((char *) http_packet, "GET", 3) == 0) {
*ptr += 4; // "GET " - 4
Expand All @@ -191,7 +263,7 @@ enum parse_http_request_status parse_http_request_line_from_packet(
} else {
fprintf(stderr, "right now, only HTTP GET and POST verbs are supported");
fflush(stderr);
return E_HTTP_METHOD_NOT_SUPPORTED;
return PARSE_E_HTTP_METHOD_NOT_SUPPORTED;
}
#ifdef DEBUG
printf("request method: %d", request->method);
Expand All @@ -216,7 +288,7 @@ enum parse_http_request_status parse_http_request_line_from_packet(
} else {
fprintf(stderr, "illegal http packet");
fflush(stderr);
return E_MALFORMED_HTTP_REQUEST_LINE;
return PARSE_E_MALFORMED_HTTP_REQUEST_LINE;
}
if (strncmp((char *) http_packet + *ptr, "1.0", 3) == 0) {
request->version = HTTP_1_0;
Expand All @@ -227,7 +299,7 @@ enum parse_http_request_status parse_http_request_line_from_packet(
} else {
fprintf(stderr, "right now, only HTTP 1.0 is supported");
fflush(stderr);
return E_HTTP_VERSION_NOT_SUPPORTED;
return PARSE_E_HTTP_VERSION_NOT_SUPPORTED;
}

if (*ptr >= http_packet_len || *ptr + 2 >= http_packet_len) {
Expand Down
15 changes: 15 additions & 0 deletions src/tiny_http/tiny_http_server_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ typedef struct http_request {

typedef struct http_response {
http_version version;
uint8_t status_code;
uint8_t *reason_phrase;
http_header *headers;
size_t headers_cnt;
uint8_t *body;
Expand All @@ -62,4 +64,17 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t
*/
void destroy_http_request(http_request *http_request);

enum render_http_response_status {
RENDER_OK = 0,
RENDER_E_MEM_ALLOC_FAILED = -1,
RENDER_E_RESPONSE_OBJ_IS_NULL = -2,
RENDER_E_OUT_PARAM_ADDR_IS_NULL = -3,
RENDER_E_HTTP_VERSION_NOT_SUPPORTED = -4,
};

enum render_http_response_status render_http_response(
const http_response *http_response,
uint8_t **out_response_octets,
size_t *out_response_len);

#endif //TINY_HTTP_SERVER_LIB_H
17 changes: 17 additions & 0 deletions test/assert_tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,28 @@ void test_request_parse_head(void) {
destroy_http_request(http_req);
}

void test_response_render_200_no_body(void) {
const http_response response = {
.version = HTTP_1_0,
.status_code = 200,
.reason_phrase = "OK",
};
uint8_t *response_octets = nullptr;
size_t response_octets_len = 0;
const enum render_http_response_status response_code = render_http_response(
&response, &response_octets, &response_octets_len);
assert(response_code == RENDER_OK);
assert(response_octets_len > 0);
assert(strncmp((char *) response_octets, "HTTP/1.0 200 OK\r\n", 32) == 0);
}

int main() {
test_request_parse_get_root_curl();
test_request_post_root_curl();
test_request_post_root_curl_with_wide_chars();
test_request_parse_head();

test_response_render_200_no_body();

return EXIT_SUCCESS;
}

0 comments on commit 9fdb7de

Please sign in to comment.