A small lightweight http server library in c

feat: initial commit

+1
.gitignore
··· 1 + build
+44
Makefile
··· 1 + CC ?= cc 2 + AR ?= ar 3 + CFLAGS ?= -std=c11 -Wall -Wextra -Wpedantic -O2 -g 4 + CPPFLAGS?= -Iinclude -Isrc 5 + LDFLAGS ?= 6 + LDLIBS ?= 7 + 8 + BUILD := build 9 + LIBNAME := prairie 10 + 11 + SRC := $(wildcard src/*.c) 12 + OBJ := $(patsubst src/%.c,$(BUILD)/%.o,$(SRC)) 13 + 14 + PUBLIC_HEADERS := $(wildcard include/prairie/*.h) 15 + 16 + .PHONY: all clean test install uninstall 17 + 18 + all: $(BUILD)/lib$(LIBNAME).a 19 + 20 + # Build directory 21 + $(BUILD): 22 + @mkdir -p $(BUILD) 23 + 24 + # Object files 25 + $(BUILD)/%.o: src/%.c | $(BUILD) 26 + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ 27 + 28 + # Static library 29 + $(BUILD)/lib$(LIBNAME).a: $(OBJ) 30 + $(AR) rcs $@ $^ 31 + 32 + clean: 33 + rm -rf $(BUILD) 34 + 35 + PREFIX ?= /usr/local 36 + install: all 37 + install -d $(DESTDIR)$(PREFIX)/include/prairie 38 + install -m 0644 $(PUBLIC_HEADERS) $(DESTDIR)$(PREFIX)/include/prairie/ 39 + install -d $(DESTDIR)$(PREFIX)/lib 40 + install -m 0644 $(BUILD)/lib$(LIBNAME).a $(DESTDIR)$(PREFIX)/lib/ 41 + 42 + uninstall: 43 + rm -f $(DESTDIR)$(PREFIX)/lib/lib$(LIBNAME).a 44 + rm -rf $(DESTDIR)$(PREFIX)/include/prairie
+4
README
··· 1 + prairie 2 + ========= 3 + 4 + A small lightweight http server library in c.
+40
include/prairie/lexer.h
··· 1 + #include "stdbool.h" 2 + 3 + #ifndef PRAIRIE_LEXER_H 4 + #define PRAIRIE_LEXER_H 5 + 6 + typedef enum { 7 + PRAIRIE_TOKEN_IDENTIFIER = 0, 8 + PRAIRIE_TOKEN_COLON = 1, 9 + PRAIRIE_TOKEN_BODY = 2 10 + } prairie_token_type_t; 11 + 12 + typedef struct prairie_token_t { 13 + prairie_token_type_t type; 14 + char *lexeme; 15 + struct prairie_token_t *next; 16 + } prairie_token_t; 17 + 18 + typedef enum { 19 + PRAIRIE_LS_METHOD, 20 + PRAIRIE_LS_ROUTE, 21 + PRAIRIE_LS_PROTOCOL, 22 + PRAIRIE_LS_FIELD_NAME, 23 + PRAIRIE_LS_FIELD_VALUE, 24 + PRAIRIE_LS_BODY 25 + } prairie_lexer_state_t; 26 + 27 + typedef struct { 28 + prairie_token_t *token_start; 29 + prairie_token_t *token_end; 30 + char *acc; 31 + prairie_lexer_state_t state; 32 + } prairie_lexer_ctx_t; 33 + 34 + prairie_token_t *prairie_lex(char *raw_file, int raw_file_length); 35 + void prairie_print_token(prairie_token_t *token); 36 + void prairie_print_tokens(prairie_token_t *token); 37 + char *prairie_token_to_string(prairie_token_t *token); 38 + char *prairie_token_type_to_string(prairie_token_type_t type); 39 + 40 + #endif
+33
include/prairie/parser.h
··· 1 + #include "lexer.h" 2 + 3 + #ifndef PRAIRIE_PARSER_H 4 + #define PRAIRIE_PARSER_H 5 + 6 + typedef enum { GET, POST } prairie_method_t; 7 + 8 + typedef enum { HTTP_1_0, HTTP_1_1, HTTP_2 } prairie_protocol_t; 9 + 10 + typedef struct prairie_header_t { 11 + char *key; 12 + char *value; 13 + struct prairie_header_t *next; 14 + } prairie_header_t; 15 + 16 + typedef struct { 17 + prairie_method_t method; 18 + char *route; 19 + prairie_protocol_t protocol; 20 + char *body; 21 + prairie_header_t *header_start; 22 + prairie_header_t *header_end; 23 + } prairie_response_t; 24 + 25 + typedef struct { 26 + prairie_token_t *current_token; 27 + prairie_response_t *response; 28 + } ParserContext; 29 + 30 + prairie_response_t *prairie_parse(prairie_token_t *token_start); 31 + void prairie_print_response(prairie_response_t *response); 32 + 33 + #endif
+8
include/prairie/prairie.h
··· 1 + #include "parser.h" 2 + 3 + #ifndef PRAIRIE_H 4 + 5 + prairie_response_t *prairie_make_response(char *raw, int length); 6 + 7 + #endif 8 + #define PRAIRIE_H
+11
include/prairie/utils.h
··· 1 + #include "stdbool.h" 2 + #include "stdlib.h" 3 + #include "string.h" 4 + 5 + #ifndef PRAIRIE_UTILS_H 6 + #define PRAIRIE_UTILS_H 7 + 8 + char *prairie_pad_right(char *value, int length, char padding); 9 + char *prairie_pad_left(char *value, int length, char padding); 10 + 11 + #endif
+130
src/lexer.c
··· 1 + #include "stdio.h" 2 + #include "stdlib.h" 3 + #include "string.h" 4 + #include <prairie/lexer.h> 5 + #include <prairie/utils.h> 6 + 7 + void add_token(prairie_lexer_ctx_t *ctx, prairie_token_type_t type, 8 + char *lexeme) { 9 + prairie_token_t *token = malloc(sizeof(prairie_token_t)); 10 + token->type = type; 11 + token->lexeme = lexeme; 12 + 13 + if (ctx->token_end != NULL) { 14 + ctx->token_end->next = token; 15 + ctx->token_end = token; 16 + } else { 17 + ctx->token_end = token; 18 + ctx->token_start = token; 19 + } 20 + } 21 + 22 + void default_acc(prairie_lexer_ctx_t *ctx) { 23 + ctx->acc = malloc(1); 24 + ctx->acc[0] = '\0'; 25 + } 26 + 27 + void pop_acc(prairie_lexer_ctx_t *ctx, prairie_token_type_t type) { 28 + if (strcmp(ctx->acc, "") == 0) 29 + return; 30 + 31 + add_token(ctx, type, ctx->acc); 32 + 33 + default_acc(ctx); 34 + } 35 + 36 + char *token_to_string_map[] = {"identifier", "colon", "body"}; 37 + 38 + char *prairie_token_to_string(prairie_token_t *token) { 39 + return token_to_string_map[token->type]; 40 + } 41 + char *prairie_token_type_to_string(prairie_token_type_t type) { 42 + return token_to_string_map[type]; 43 + } 44 + 45 + void prairie_print_token(prairie_token_t *token) { 46 + int map_length = 3; 47 + int max_value_length = 0; 48 + 49 + for (int i = 0; i < map_length; i++) { 50 + int value_length = strlen(token_to_string_map[i]); 51 + if (value_length > max_value_length) 52 + max_value_length = value_length; 53 + } 54 + 55 + printf("%s %s\n", 56 + prairie_pad_right(token_to_string_map[token->type], 57 + max_value_length + 1, ' '), 58 + token->lexeme); 59 + } 60 + 61 + void prairie_print_tokens(prairie_token_t *token) { 62 + prairie_token_t *tmp_token = token; 63 + while (tmp_token != NULL) { 64 + prairie_print_token(tmp_token); 65 + tmp_token = tmp_token->next; 66 + } 67 + } 68 + 69 + prairie_token_t *prairie_lex(char *raw_file, int raw_file_length) { 70 + prairie_lexer_ctx_t *ctx = malloc(sizeof(prairie_lexer_ctx_t)); 71 + ctx->token_start = NULL; 72 + ctx->token_end = NULL; 73 + ctx->acc = NULL; 74 + ctx->state = PRAIRIE_LS_METHOD; 75 + default_acc(ctx); 76 + 77 + for (int i = 0; i < raw_file_length; i++) { 78 + switch (raw_file[i]) { 79 + case '\n': 80 + if (ctx->state != PRAIRIE_LS_BODY) { 81 + if (ctx->state == PRAIRIE_LS_FIELD_NAME) { 82 + ctx->state = PRAIRIE_LS_BODY; 83 + break; 84 + } 85 + 86 + pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER); 87 + ctx->state = PRAIRIE_LS_FIELD_NAME; 88 + break; 89 + } 90 + case ' ': 91 + if (ctx->state == PRAIRIE_LS_METHOD) { 92 + pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER); 93 + ctx->state = PRAIRIE_LS_ROUTE; 94 + break; 95 + } 96 + if (ctx->state == PRAIRIE_LS_ROUTE) { 97 + pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER); 98 + ctx->state = PRAIRIE_LS_PROTOCOL; 99 + break; 100 + } 101 + case ':': 102 + if (ctx->state == PRAIRIE_LS_FIELD_NAME) { 103 + i++; 104 + pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER); 105 + add_token(ctx, PRAIRIE_TOKEN_COLON, ":"); 106 + ctx->state = PRAIRIE_LS_FIELD_VALUE; 107 + break; 108 + } 109 + default: 110 + int acc_length = strlen(ctx->acc); 111 + char *tmp = realloc(ctx->acc, acc_length + 2); 112 + if (!tmp) { 113 + printf("failed to allocate accumulator in lexer"); 114 + return 0; 115 + } 116 + 117 + ctx->acc = tmp; 118 + ctx->acc[acc_length] = raw_file[i]; 119 + ctx->acc[acc_length + 1] = '\0'; 120 + break; 121 + } 122 + } 123 + 124 + if (ctx->state == PRAIRIE_LS_BODY) 125 + pop_acc(ctx, PRAIRIE_TOKEN_BODY); 126 + 127 + prairie_token_t *start = ctx->token_start; 128 + free(ctx); 129 + return start; 130 + }
+136
src/parser.c
··· 1 + #include "stdarg.h" 2 + #include "stdbool.h" 3 + #include "stdio.h" 4 + #include "stdlib.h" 5 + #include "string.h" 6 + #include <prairie/lexer.h> 7 + #include <prairie/parser.h> 8 + #include <prairie/utils.h> 9 + 10 + void advance(ParserContext *ctx) { 11 + ctx->current_token = ctx->current_token->next; 12 + } 13 + 14 + /** 15 + * Returns true if the assertion passes 16 + */ 17 + bool assert(ParserContext *ctx, prairie_token_type_t type) { 18 + if (ctx->current_token->type != type) { 19 + printf("unexpected token, expected %s, found %s\n", 20 + prairie_token_type_to_string(type), 21 + prairie_token_to_string(ctx->current_token)); 22 + return false; 23 + } 24 + return true; 25 + } 26 + 27 + bool assert_advance(ParserContext *ctx, prairie_token_type_t type) { 28 + if (assert(ctx, type)) { 29 + advance(ctx); 30 + return true; 31 + } 32 + return false; 33 + } 34 + 35 + prairie_method_t string_to_method(char *value) { 36 + if (strcmp(value, "GET") == 0) 37 + return GET; 38 + if (strcmp(value, "POST") == 0) 39 + return POST; 40 + 41 + // Might want to do some error handling here but default > crash 42 + return GET; 43 + } 44 + 45 + prairie_protocol_t string_to_protocol(char *value) { 46 + if (strcmp(value, "HTTP/1.0") == 0) 47 + return HTTP_1_0; 48 + if (strcmp(value, "HTTP/1.1") == 0) 49 + return HTTP_1_1; 50 + if (strcmp(value, "HTTP/2") == 0) 51 + return HTTP_2; 52 + 53 + // Might want to do some error handling here but default > crash 54 + return HTTP_1_1; 55 + } 56 + 57 + #define free_and_return \ 58 + { \ 59 + printf("Error occured\n"); \ 60 + free(ctx); \ 61 + free(response); \ 62 + return NULL; \ 63 + } 64 + 65 + void prairie_print_response(prairie_response_t *response) { 66 + printf("%d %s %d\n", response->method, response->route, response->protocol); 67 + prairie_header_t *header = response->header_start; 68 + while (header != NULL) { 69 + printf("%s: %s\n", header->key, header->value); 70 + header = header->next; 71 + } 72 + printf("%s\n", response->body); 73 + } 74 + 75 + prairie_response_t *prairie_parse(prairie_token_t *token_start) { 76 + prairie_response_t *response = malloc(sizeof(prairie_response_t)); 77 + ParserContext *ctx = malloc(sizeof(ParserContext)); 78 + ctx->current_token = token_start; 79 + ctx->response = response; 80 + 81 + if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER)) 82 + free_and_return; 83 + 84 + // printf("method: %s\n", ctx->current_token->lexeme); 85 + ctx->response->method = string_to_method(ctx->current_token->lexeme); 86 + advance(ctx); 87 + 88 + if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER)) 89 + free_and_return; 90 + 91 + // printf("route: %s\n", ctx->current_token->lexeme); 92 + ctx->response->route = ctx->current_token->lexeme; 93 + advance(ctx); 94 + 95 + if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER)) 96 + free_and_return; 97 + 98 + // printf("protocol: %s\n", ctx->current_token->lexeme); 99 + ctx->response->protocol = string_to_protocol(ctx->current_token->lexeme); 100 + advance(ctx); 101 + 102 + while (ctx->current_token != NULL) { 103 + if (ctx->current_token->type == PRAIRIE_TOKEN_IDENTIFIER) { 104 + prairie_header_t *header = malloc(sizeof(prairie_header_t)); 105 + header->key = ctx->current_token->lexeme; 106 + header->next = NULL; 107 + advance(ctx); 108 + 109 + if (!assert_advance(ctx, PRAIRIE_TOKEN_COLON)) 110 + free_and_return; 111 + 112 + header->value = ctx->current_token->lexeme; 113 + 114 + if (ctx->response->header_start == NULL) { 115 + ctx->response->header_start = header; 116 + ctx->response->header_end = header; 117 + } else { 118 + ctx->response->header_end->next = header; 119 + ctx->response->header_end = header; 120 + } 121 + advance(ctx); 122 + } else if (ctx->current_token->type == PRAIRIE_TOKEN_BODY) { 123 + ctx->response->body = ctx->current_token->lexeme; 124 + free(ctx); 125 + 126 + return response; 127 + } else { 128 + printf("unexpected token. expected identifier or body, found %s\n", 129 + prairie_token_to_string(ctx->current_token)); 130 + free_and_return; 131 + } 132 + } 133 + 134 + free(ctx); 135 + return response; 136 + }
+11
src/prairie.c
··· 1 + #include <prairie/lexer.h> 2 + #include <prairie/parser.h> 3 + 4 + prairie_response_t *prairie_make_response(char *raw, int length) { 5 + prairie_token_t *token = prairie_lex(raw, length); 6 + // print_tokens(token); 7 + 8 + prairie_response_t *response = prairie_parse(token); 9 + // print_response(response); 10 + return response; 11 + }
+42
src/utils.c
··· 1 + #include "stdbool.h" 2 + #include "stdlib.h" 3 + #include "string.h" 4 + 5 + char *prairie_pad_right(char *value, int length, char padding) { 6 + int value_length = strlen(value); 7 + 8 + // Don't do any processing if the desired length is below the values length 9 + if (length <= value_length) 10 + return value; 11 + 12 + char *padded_value = (char *)malloc(length + 1); 13 + for (int i = 0; i < length; i++) { 14 + if (i < value_length) { 15 + padded_value[i] = value[i]; 16 + } else { 17 + padded_value[i] = padding; 18 + } 19 + } 20 + padded_value[length] = '\0'; 21 + 22 + return padded_value; 23 + } 24 + 25 + char *prairie_pad_left(char *value, int length, char padding) { 26 + int value_length = strlen(value); 27 + 28 + // Don't do any processing if the desired length is below the values length 29 + if (length <= value_length) 30 + return value; 31 + 32 + char *padded_value = (char *)malloc(length + 1); 33 + for (int i = 0; i < length; i++) { 34 + if (i < length - value_length) { 35 + padded_value[i] = padding; 36 + } else { 37 + padded_value[i] = value[i - (length - value_length)]; 38 + } 39 + } 40 + padded_value[length] = '\0'; 41 + return padded_value; 42 + }