├── .gitignore ├── Makefile ├── README.md ├── http_handler.c ├── http_handler.h ├── my_http.h ├── my_socket.c ├── my_socket.h ├── request.c ├── request.h ├── response.c ├── response.h └── server.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | tags 3 | log 4 | log.sh 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | 3 | CFLAGS = -g 4 | 5 | SRCS = http_handler.c my_socket.c request.c response.c server.c 6 | OBJS = $(SRCS:.c=.o) 7 | HEADERS = http_handler.h my_http.h my_socket.h request.h response.h 8 | 9 | TARGET = server 10 | 11 | all: $(TARGET) 12 | 13 | $(TARGET): $(OBJS) 14 | $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) 15 | 16 | %.o: %.c $(HEADERS) 17 | $(CC) $(CFLAGS) -c $< -o $@ 18 | 19 | clean: 20 | rm -f $(OBJS) $(TARGET) 21 | 22 | rebuild: clean all 23 | 24 | tags: 25 | ctags -R . 26 | 27 | .PHONY: all clean rebuild tags 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo 3 |

4 | 5 |

HTTP Server in C

6 | 7 |

8 | This project implements a simple HTTP server in C. It handles various HTTP methods, processes requests, and generates responses based on client requests. The server is built using low-level socket programming and custom HTTP request/response handling. 9 |

10 | 11 | --- 12 | 13 |

Connect with me:

14 |

15 | 16 | X Profile 17 | 18 |   19 | 20 | Discord Server 21 | 22 |

23 | 24 | --- 25 | 26 | ### Features: 27 | - **HTTP Request Parsing:** Support for multiple HTTP methods including GET, POST, PUT, DELETE, and more. 28 | - **Request Dispatching:** Dispatches requests based on URI and method. 29 | - **Non-Blocking I/O:** Uses non-blocking sockets to handle multiple clients concurrently. 30 | - **Customizable Response:** Dynamically builds and sends HTTP responses based on the request and server logic. 31 | - **Modular Design:** Organized into multiple files for handling sockets, HTTP requests, responses, and server operations. 32 | 33 | ### Files Overview: 34 | - `http_handler.c` / `http_handler.h`: Manages the request handling and dispatch logic. 35 | - `my_socket.c` / `my_socket.h`: Contains functions for setting up and managing server sockets. 36 | - `request.c` / `request.h`: Responsible for parsing HTTP requests and managing request headers and body. 37 | - `response.c` / `response.h`: Handles building and formatting HTTP responses, including headers and body. 38 | - `server.c`: Main entry point of the server, manages client connections and the server loop. 39 | - `my_http.h`: Contains common HTTP-related constants and definitions used across the project. 40 | 41 | ### Usage: 42 | 1. **Clone the repository:** 43 | `git clone [repository URL]` 44 | 2. **Compile the project:** 45 | Navigate to the project directory and run `make`. 46 | 3. **Run the server:** 47 | Execute `./server` and handle incoming HTTP requests on the specified port. 48 | 49 | ### Example: 50 | Start the server and visit `http://localhost:4221/` to interact with it. It handles routes like `/`, `/echo/`, and `/user-agent` and responds with the appropriate content. 51 | 52 | ### Requirements: 53 | - **Compiler:** GCC (or another C compiler) 54 | - **Environment:** POSIX-compliant system (Linux/macOS) 55 | 56 | This project serves as a base for further extensions like adding more HTTP methods, handling file uploads, or implementing a full-fledged web server. 57 | 58 | --- 59 | 60 | -------------------------------------------------------------------------------- /http_handler.c: -------------------------------------------------------------------------------- 1 | #include "http_handler.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int build_and_send_response(int client_fd, const char *version, int status, const char *content, 10 | const char *content_type); 11 | static int send_response(const char *response_str, int client_fd); 12 | 13 | static int dispatch_method(http_request *request, int client_fd); 14 | static int handle_http_get(http_request *request, int client_fd); 15 | static int handle_http_unkown(http_request *request, int client_fd); 16 | static int handle_http_post(http_request *request, int client_fd); 17 | static int handle_http_put(http_request *request, int client_fd); 18 | static int handle_http_delete(http_request *request, int client_fd); 19 | static int handle_http_head(http_request *request, int client_fd); 20 | static int handle_http_options(http_request *request, int client_fd); 21 | static int handle_http_patch(http_request *request, int client_fd); 22 | static int handle_http_trace(http_request *request, int client_fd); 23 | static int handle_http_connect(http_request *request, int client_fd); 24 | 25 | static int dispatch_uri(http_request *request, int client_fd); 26 | static int handle_root(http_request *request, int client_fd); 27 | static int handle_echo(http_request *request, int client_fd); 28 | static int handle_user_agent(http_request *request, int client_fd); 29 | static int handle_not_found(http_request *request, int client_fd); 30 | 31 | int (*handle_http_method[])(http_request *request, int client_fd) = { 32 | handle_http_get, 33 | handle_http_post, 34 | handle_http_put, 35 | handle_http_delete, 36 | handle_http_head, 37 | handle_http_options, 38 | handle_http_patch, 39 | handle_http_trace, 40 | handle_http_connect, 41 | handle_http_unkown, 42 | NULL, 43 | }; 44 | 45 | struct 46 | { 47 | const char *uri; 48 | int (*handler)(http_request *request, int client_fd); 49 | } uri_entry[] = { 50 | {"/", handle_root}, 51 | {"/echo/", handle_echo}, 52 | {"/user-agent", handle_user_agent}, 53 | {NULL, NULL}, 54 | }; 55 | 56 | int handle_request(http_request *request, int client_fd) 57 | { 58 | return dispatch_method(request, client_fd); 59 | } 60 | 61 | static int dispatch_method(http_request *request, int client_fd) 62 | { 63 | return handle_http_method[request->request_line.method](request, client_fd); 64 | } 65 | 66 | static int build_and_send_response(int client_fd, const char *version, int status, const char *content, 67 | const char *content_type) 68 | { 69 | const char *http_header_format = "Content-Type:%s\r\n"; 70 | char http_header[HTTP_HEADER_VALUE_MAX_LEN]; 71 | snprintf(http_header, sizeof(http_header), http_header_format, content_type); 72 | 73 | char *response_str = build_http_response(version, status, content, strlen(content), http_header); 74 | if (response_str == NULL) 75 | return -1; 76 | 77 | int retval = send_response(response_str, client_fd); 78 | free(response_str); 79 | 80 | return retval; 81 | } 82 | 83 | static int send_response(const char *response_str, int client_fd) 84 | { 85 | ssize_t n = send(client_fd, response_str, strlen(response_str), 0); 86 | if (n == -1) 87 | { 88 | perror("Failed to send response to client"); 89 | return -1; 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | static int dispatch_uri(http_request *request, int client_fd) 96 | { 97 | if (request->request_line.uri == NULL) 98 | return handle_not_found(request, client_fd); 99 | 100 | if (request->request_line.uri[0] == '/' && request->request_line.uri[1] == '\0') 101 | return uri_entry[0].handler(request, client_fd); 102 | 103 | for (int i = 1; uri_entry[i].uri != NULL; i++) 104 | { 105 | size_t uri_len = strlen(uri_entry[i].uri); 106 | 107 | if (strncmp(request->request_line.uri, uri_entry[i].uri, uri_len) == 0) 108 | return uri_entry[i].handler(request, client_fd); 109 | } 110 | 111 | return handle_not_found(request, client_fd); 112 | } 113 | 114 | static int handle_root(http_request *request, int client_fd) 115 | { 116 | return build_and_send_response(client_fd, request->request_line.version, HTTP_OK, "OK", "text/html"); 117 | } 118 | 119 | static int handle_echo(http_request *request, int client_fd) 120 | { 121 | char *echo = request->request_line.uri + strlen("/echo/"); 122 | return build_and_send_response(client_fd, request->request_line.version, HTTP_OK, echo, "text/plain"); 123 | } 124 | 125 | static int handle_user_agent(http_request *request, int client_fd) 126 | { 127 | for (int i = 0; i < request->header_count; i++) 128 | { 129 | if (strcmp(request->headers[i].name, "User-Agent") == 0) 130 | return build_and_send_response(client_fd, request->request_line.version, HTTP_OK, request->headers[i].value, 131 | "text/plain"); 132 | } 133 | 134 | return build_and_send_response(client_fd, request->request_line.version, HTTP_NOT_FOUND, "Not Found", "text/plain"); 135 | } 136 | 137 | static int handle_not_found(http_request *request, int client_fd) 138 | { 139 | return build_and_send_response(client_fd, request->request_line.version, HTTP_NOT_FOUND, "Not Found", "text/plain"); 140 | } 141 | 142 | static int handle_http_get(http_request *request, int client_fd) 143 | { 144 | return dispatch_uri(request, client_fd); 145 | } 146 | 147 | static int handle_http_unkown(http_request *request, int client_fd) 148 | { 149 | return dispatch_uri(request, client_fd); 150 | } 151 | 152 | static int handle_http_post(http_request *request, int client_fd) 153 | { 154 | return dispatch_uri(request, client_fd); 155 | } 156 | 157 | static int handle_http_put(http_request *request, int client_fd) 158 | { 159 | return dispatch_uri(request, client_fd); 160 | } 161 | 162 | static int handle_http_delete(http_request *request, int client_fd) 163 | { 164 | return dispatch_uri(request, client_fd); 165 | } 166 | 167 | static int handle_http_head(http_request *request, int client_fd) 168 | { 169 | return dispatch_uri(request, client_fd); 170 | } 171 | 172 | static int handle_http_options(http_request *request, int client_fd) 173 | { 174 | return dispatch_uri(request, client_fd); 175 | } 176 | 177 | static int handle_http_patch(http_request *request, int client_fd) 178 | { 179 | return dispatch_uri(request, client_fd); 180 | } 181 | 182 | static int handle_http_trace(http_request *request, int client_fd) 183 | { 184 | return dispatch_uri(request, client_fd); 185 | } 186 | 187 | static int handle_http_connect(http_request *request, int client_fd) 188 | { 189 | return dispatch_uri(request, client_fd); 190 | } 191 | -------------------------------------------------------------------------------- /http_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_HANDLER_H 2 | #define HTTP_HANDLER_H 3 | 4 | #include "request.h" 5 | #include "response.h" 6 | 7 | int handle_request(http_request *request, int client_fd); 8 | 9 | #endif // HTTP_HANDLER_H -------------------------------------------------------------------------------- /my_http.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_HTTP_H 2 | #define MY_HTTP_H 3 | 4 | #define MAX_RECV_BUF 2048 5 | #define MAX_STATUS_LEN 64 6 | #define HTTP_MAX_HEADERS 100 7 | #define HTTP_STATUS_COUNT 3 8 | #define HTTP_REASON_MAX_LEN 64 9 | #define HTTP_METHOD_MAX_LEN 16 10 | #define HTTP_PATH_MAX_LEN 256 11 | #define HTTP_VERSION_MAX_LEN 16 12 | #define HTTP_HEADER_NAME_MAX_LEN 64 13 | #define HTTP_HEADER_VALUE_MAX_LEN 256 14 | #define HTTP_MAX_RESPONSE_SIZE 8192 15 | 16 | #endif // MY_HTTP_H -------------------------------------------------------------------------------- /my_socket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "my_socket.h" 13 | 14 | int create_server_socket(void) 15 | { 16 | int server_fd = socket(AF_INET, SOCK_STREAM, 0); 17 | if (server_fd == -1) 18 | { 19 | printf("Socket creation failed: %s...\n", strerror(errno)); 20 | return -1; 21 | } 22 | 23 | int reuse = 1; 24 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) 25 | { 26 | printf("SO_REUSEADDR failed: %s \n", strerror(errno)); 27 | return -1; 28 | } 29 | 30 | return server_fd; 31 | } 32 | 33 | void set_server_address(struct sockaddr_in *serv_addr, int port) 34 | { 35 | serv_addr->sin_family = AF_INET; 36 | serv_addr->sin_port = htons(port); 37 | serv_addr->sin_addr.s_addr = htonl(INADDR_ANY); 38 | } 39 | 40 | int bind_server_socket(int server_fd, struct sockaddr_in *serv_addr) 41 | { 42 | if (bind(server_fd, (struct sockaddr *)serv_addr, sizeof(*serv_addr)) != 0) 43 | { 44 | printf("Bind failed: %s \n", strerror(errno)); 45 | return -1; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | int listen_server_socket(int server_fd, int connection_backlog) 52 | { 53 | if (listen(server_fd, connection_backlog) != 0) 54 | { 55 | printf("Listen failed: %s \n", strerror(errno)); 56 | return -1; 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | int accept_client(int server_fd) 63 | { 64 | struct sockaddr_in client_addr; 65 | socklen_t client_addr_len = sizeof(client_addr); 66 | int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len); 67 | if (client_fd == -1) 68 | { 69 | if (errno == EAGAIN || errno == EWOULDBLOCK) 70 | return -1; 71 | perror("Failed to accept client connection"); 72 | return -1; 73 | } 74 | 75 | if (set_socket_nonblocking(client_fd) == -1) 76 | { 77 | perror("Failed to set client socket to non-blocking mode"); 78 | close(client_fd); 79 | return -1; 80 | } 81 | 82 | return client_fd; 83 | } 84 | 85 | int set_socket_nonblocking(int socket_fd) 86 | { 87 | int flags = fcntl(socket_fd, F_GETFL, 0); 88 | if (flags == -1) 89 | { 90 | return -1; 91 | } 92 | 93 | flags |= O_NONBLOCK; 94 | return fcntl(socket_fd, F_SETFL, flags); 95 | } 96 | 97 | int init_server(void) 98 | { 99 | int server_fd = create_server_socket(); 100 | if (server_fd == -1) 101 | return -1; 102 | 103 | struct sockaddr_in serv_addr; 104 | set_server_address(&serv_addr, PORT); 105 | 106 | if (bind_server_socket(server_fd, &serv_addr) != 0) 107 | { 108 | close(server_fd); 109 | return -1; 110 | } 111 | 112 | if (listen_server_socket(server_fd, BACKLOG) != 0) 113 | { 114 | close(server_fd); 115 | return -1; 116 | } 117 | 118 | if (set_socket_nonblocking(server_fd) == -1) 119 | { 120 | perror("Failed to set server socket to non-blocking mode"); 121 | close(server_fd); 122 | return -1; 123 | } 124 | 125 | return server_fd; 126 | } 127 | -------------------------------------------------------------------------------- /my_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_SOCKET_H 2 | #define MY_SOCKET_H 3 | 4 | #define BACKLOG 5 5 | #define PORT 4221 6 | 7 | #include 8 | 9 | int init_server(void); 10 | int accept_client(int server_fd); 11 | int set_socket_nonblocking(int socket_fd); 12 | 13 | #endif // MY_SOCKET_H -------------------------------------------------------------------------------- /request.c: -------------------------------------------------------------------------------- 1 | #include "request.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | const http_method_entry http_methods[HTTP_METHOD_COUNT] = { 9 | {HTTP_METHOD_GET, "GET"}, {HTTP_METHOD_POST, "POST"}, {HTTP_METHOD_PUT, "PUT"}, 10 | {HTTP_METHOD_DELETE, "DELETE"}, {HTTP_METHOD_HEAD, "HEAD"}, {HTTP_METHOD_OPTIONS, "OPTIONS"}, 11 | {HTTP_METHOD_PATCH, "PATCH"}, {HTTP_METHOD_TRACE, "TRACE"}, {HTTP_METHOD_CONNECT, "CONNECT"}}; 12 | 13 | static char *duplicate_string_or_exit(const char *source) 14 | { 15 | char *dup = strdup(source); 16 | if (!dup) 17 | { 18 | fprintf(stderr, "Failed to allocate memory for string duplication.\n"); 19 | return NULL; 20 | } 21 | 22 | return dup; 23 | } 24 | 25 | void init_http_request(http_request *request) 26 | { 27 | if (request) 28 | { 29 | request->request_line.method = HTTP_METHOD_UNKNOWN; 30 | request->request_line.uri = NULL; 31 | request->request_line.version = NULL; 32 | 33 | // initialize headers 34 | request->header_count = 0; 35 | for (int i = 0; i < HTTP_MAX_HEADERS; i++) 36 | { 37 | request->headers[i].name = NULL; 38 | request->headers[i].value = NULL; 39 | } 40 | 41 | // initialize body 42 | request->body = NULL; 43 | request->body_length = 0; 44 | } 45 | } 46 | 47 | void cleanup_http_request(http_request *request) 48 | { 49 | if (request) 50 | { 51 | if (request->request_line.uri) 52 | { 53 | free(request->request_line.uri); 54 | request->request_line.uri = NULL; 55 | } 56 | if (request->request_line.version) 57 | { 58 | free(request->request_line.version); 59 | request->request_line.version = NULL; 60 | } 61 | 62 | // free headers 63 | for (int i = 0; i < request->header_count; i++) 64 | { 65 | if (request->headers[i].name) 66 | { 67 | free((void *)request->headers[i].name); 68 | request->headers[i].name = NULL; 69 | } 70 | if (request->headers[i].value) 71 | { 72 | free((void *)request->headers[i].value); 73 | request->headers[i].value = NULL; 74 | } 75 | } 76 | request->header_count = 0; 77 | if (request->body) 78 | { 79 | free(request->body); 80 | request->body = NULL; 81 | request->body_length = 0; 82 | } 83 | } 84 | } 85 | 86 | http_method find_http_method(const char *method_str) 87 | { 88 | if (!method_str) 89 | return HTTP_METHOD_UNKNOWN; 90 | 91 | for (size_t i = 0; i < HTTP_METHOD_COUNT; i++) 92 | if (strcasecmp(method_str, http_methods[i].name) == 0) 93 | return http_methods[i].method; 94 | 95 | return HTTP_METHOD_UNKNOWN; 96 | } 97 | 98 | int store_http_header(http_request *request, const char *name, const char *value) 99 | { 100 | if (request && name && value && request->header_count < HTTP_MAX_HEADERS) 101 | { 102 | request->headers[request->header_count].name = duplicate_string_or_exit(name); 103 | request->headers[request->header_count].value = duplicate_string_or_exit(value); 104 | request->header_count++; 105 | return 0; 106 | } 107 | fprintf(stderr, "Failed to store header exceeded maximum headers or invalid parameters : %s: %s\n", name, value); 108 | return -1; // 109 | } 110 | 111 | void store_http_body(http_request *request, const char *body, size_t length) 112 | { 113 | if (request) 114 | { 115 | // Free existing body if present 116 | if (request->body) 117 | { 118 | free(request->body); 119 | } 120 | // Duplicate the body content 121 | request->body = body ? strndup(body, length) : NULL; 122 | if (body) 123 | { 124 | request->body_length = length; 125 | } 126 | else 127 | { 128 | request->body_length = 0; 129 | } 130 | // Automatically set Content-Length header if body is present 131 | if (body) 132 | { 133 | char content_length[20]; 134 | snprintf(content_length, sizeof(content_length), "%zu", length); 135 | // Check if Content-Length header already exists 136 | int found = -1; 137 | for (int i = 0; i < request->header_count; i++) 138 | { 139 | if (strcasecmp(request->headers[i].name, "Content-Length") == 0) 140 | { 141 | found = i; 142 | break; 143 | } 144 | } 145 | if (found != -1) 146 | { 147 | // Update existing Content-Length header 148 | free(request->headers[found].value); 149 | request->headers[found].value = duplicate_string_or_exit(content_length); 150 | } 151 | else 152 | { 153 | // Add new Content-Length header 154 | if (store_http_header(request, "Content-Length", content_length) != 0) 155 | { 156 | fprintf(stderr, "Error: Unable to add Content-Length header.\n"); 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | int parse_http_request(const char *raw_request, size_t request_length, http_request *request) 164 | { 165 | if (!raw_request || !request) 166 | return -1; 167 | 168 | // Initialize the request structure 169 | init_http_request(request); 170 | 171 | // Make a copy of the raw request to tokenize 172 | char *request_copy = strndup(raw_request, request_length); 173 | if (!request_copy) 174 | { 175 | fprintf(stderr, "Error: Memory allocation failed while copying raw request.\n"); 176 | cleanup_http_request(request); 177 | return -1; 178 | } 179 | 180 | char *saveptr; 181 | 182 | // Parse the request line 183 | char *line = strtok_r(request_copy, "\r\n", &saveptr); 184 | if (!line) 185 | { 186 | fprintf(stderr, "Error: Invalid HTTP request. Missing request line.\n"); 187 | free(request_copy); 188 | cleanup_http_request(request); 189 | return -1; 190 | } 191 | 192 | // Tokenize the request line into method, URI, and version 193 | char *method_str = strtok(line, " "); 194 | char *uri = strtok(NULL, " "); 195 | char *version = strtok(NULL, " "); 196 | 197 | if (!method_str || !uri || !version) 198 | { 199 | fprintf(stderr, "Error: Malformed request line.\n"); 200 | free(request_copy); 201 | cleanup_http_request(request); 202 | return -1; 203 | } 204 | 205 | // Set the method 206 | request->request_line.method = find_http_method(method_str); 207 | 208 | // Duplicate and set the URI 209 | request->request_line.uri = duplicate_string_or_exit(uri); 210 | 211 | // Duplicate and set the HTTP version 212 | request->request_line.version = duplicate_string_or_exit(version); 213 | 214 | // Parse headers 215 | while ((line = strtok_r(NULL, "\r\n", &saveptr)) != NULL && strlen(line) > 0) 216 | { 217 | char *colon = strchr(line, ':'); 218 | if (!colon) 219 | { 220 | fprintf(stderr, "Error: Malformed header line (missing colon).\n"); 221 | free(request_copy); 222 | cleanup_http_request(request); 223 | return -1; 224 | } 225 | *colon = '\0'; // Split the line into name and value 226 | char *name = line; 227 | char *value = colon + 1; 228 | 229 | // Trim leading and trailing whitespace from name 230 | while (isspace((unsigned char)*name)) 231 | name++; 232 | char *end = name + strlen(name) - 1; 233 | while (end > name && isspace((unsigned char)*end)) 234 | { 235 | *end = '\0'; 236 | end--; 237 | } 238 | 239 | // Trim leading and trailing whitespace from value 240 | while (isspace((unsigned char)*value)) 241 | value++; 242 | end = value + strlen(value) - 1; 243 | while (end > value && isspace((unsigned char)*end)) 244 | { 245 | *end = '\0'; 246 | end--; 247 | } 248 | 249 | // Store the header 250 | if (store_http_header(request, name, value) != 0) 251 | { 252 | fprintf(stderr, "Error: Failed to store header: %s: %s\n", name, value); 253 | free(request_copy); 254 | cleanup_http_request(request); 255 | return -1; 256 | } 257 | } 258 | 259 | // If there's a body, parse it based on Content-Length 260 | const char *body_start = strstr(raw_request, "\r\n\r\n"); 261 | if (body_start) 262 | { 263 | body_start += 4; // Move past "\r\n\r\n" 264 | size_t body_len = request_length - (body_start - raw_request); 265 | if (body_len > 0) 266 | { 267 | store_http_body(request, body_start, body_len); 268 | } 269 | } 270 | 271 | free(request_copy); 272 | return 0; 273 | } 274 | -------------------------------------------------------------------------------- /request.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUEST_H 2 | #define REQUEST_H 3 | 4 | #include "my_http.h" 5 | #include // size_t 6 | 7 | #define HTTP_METHOD_COUNT 9 8 | typedef enum 9 | { 10 | HTTP_METHOD_GET, 11 | HTTP_METHOD_POST, 12 | HTTP_METHOD_PUT, 13 | HTTP_METHOD_DELETE, 14 | HTTP_METHOD_HEAD, 15 | HTTP_METHOD_OPTIONS, 16 | HTTP_METHOD_PATCH, 17 | HTTP_METHOD_TRACE, 18 | HTTP_METHOD_CONNECT, 19 | HTTP_METHOD_UNKNOWN 20 | } http_method; 21 | 22 | typedef struct 23 | { 24 | http_method method; 25 | const char *name; 26 | } http_method_entry; 27 | 28 | extern const http_method_entry http_methods[HTTP_METHOD_COUNT]; 29 | 30 | typedef struct 31 | { 32 | http_method method; 33 | char *uri; // Mutable string for URI 34 | char *version; // Mutable string for HTTP version 35 | } http_request_line; 36 | 37 | typedef struct 38 | { 39 | char *name; // (e.g., "Content-Type") 40 | char *value; // (e.g., "application/json") 41 | } http_request_header; 42 | 43 | typedef struct 44 | { 45 | http_request_line request_line; 46 | http_request_header headers[HTTP_MAX_HEADERS]; 47 | int header_count; 48 | char *body; // Mutable string for body 49 | size_t body_length; 50 | } http_request; 51 | 52 | /** 53 | * Initializes an http_request structure. 54 | * 55 | * @param request Pointer to the http_request structure to initialize. 56 | */ 57 | void init_http_request(http_request *request); 58 | 59 | /** 60 | * Cleans up an http_request structure, freeing any allocated memory. 61 | * 62 | * @param request Pointer to the http_request structure to clean up. 63 | */ 64 | void cleanup_http_request(http_request *request); 65 | 66 | /** 67 | * Parses a raw HTTP request string into an http_request structure. 68 | * 69 | * @param raw_request Pointer to the raw HTTP request string. 70 | * @param request_length Length of the raw HTTP request string. 71 | * @param request Pointer to the http_request structure to populate. 72 | * @return 0 on success, non-zero on failure. 73 | */ 74 | int parse_http_request(const char *raw_request, size_t request_length, http_request *request); 75 | 76 | /** 77 | * Finds an HTTP method enum based on its string representation. 78 | * 79 | * @param method_str String representation of the HTTP method. 80 | * @return Corresponding http_method enum, or HTTP_METHOD_UNKNOWN if not found. 81 | */ 82 | http_method find_http_method(const char *method_str); 83 | 84 | /** 85 | * Stores a header in the HTTP request. 86 | * 87 | * @param request Pointer to the http_request structure. 88 | * @param name Name of the header. 89 | * @param value Value of the header. 90 | * @return 0 on success, non-zero on failure (e.g., if maximum headers exceeded). 91 | */ 92 | int store_http_header(http_request *request, const char *name, const char *value); 93 | 94 | /** 95 | * Stores the body of the HTTP request. 96 | * 97 | * @param request Pointer to the http_request structure. 98 | * @param body Pointer to the body data. 99 | * @param length Length of the body data. 100 | */ 101 | void store_http_body(http_request *request, const char *body, size_t length); 102 | 103 | #endif // REQUEST_H 104 | -------------------------------------------------------------------------------- /response.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "my_http.h" 7 | #include "response.h" 8 | 9 | char *status_line_error = "HTTP/1.1 500 Internal Server Error\r\n\r\n"; 10 | 11 | const http_status_entry http_statuses[HTTP_STATUS_COUNT] = { 12 | {HTTP_OK, "OK"}, {HTTP_NOT_FOUND, "Not Found"}, {HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error"}}; 13 | 14 | static void set_http_headers(http_response *response, const char *headers); 15 | 16 | void print_hex(const char *str, size_t len) 17 | { 18 | for (size_t i = 0; i < len; i++) 19 | { 20 | printf("%02x ", str[i]); 21 | } 22 | printf("\n"); 23 | } 24 | 25 | void debug_response(http_response *response) 26 | { 27 | printf("Version: %s\t", response->version); 28 | print_hex(response->version, strlen(response->version)); 29 | printf("Status: %d %s\t", response->status.code, response->status.reason); 30 | print_hex(response->status.reason, strlen(response->status.reason)); 31 | printf("Headers:\t"); 32 | for (int i = 0; i < response->header_count; i++) 33 | { 34 | printf("%s: %s\t", response->headers[i].name, response->headers[i].value); 35 | print_hex(response->headers[i].name, strlen(response->headers[i].name)); 36 | } 37 | printf("Body: %s\t", response->body); 38 | print_hex(response->body, response->body_length); 39 | } 40 | 41 | char *build_http_response(const char *version, http_status_code code, const char *body, size_t length, 42 | const char *headers) 43 | { 44 | http_response *response = malloc(sizeof(http_response)); 45 | if (!response) 46 | { 47 | fprintf(stderr, "Error: Unable to allocate memory for response\n"); 48 | return NULL; 49 | } 50 | 51 | init_http_response(response, version); 52 | set_http_status(response, code); 53 | 54 | if (headers) 55 | { 56 | set_http_headers(response, headers); 57 | } 58 | 59 | if (body) 60 | { 61 | set_http_body(response, body, length); 62 | } 63 | 64 | char *response_str = format_http_response(response); 65 | 66 | #ifdef DEBUG 67 | printf("=== Response ===\n"); 68 | debug_response(response); 69 | printf("=== Response ===\n"); 70 | #endif 71 | 72 | cleanup_http_response(response); 73 | 74 | return response_str; 75 | } 76 | 77 | void init_http_response(http_response *response, const char *version) 78 | { 79 | if (response) 80 | { 81 | response->version = version ? strdup(version) : strdup("HTTP/1.1"); 82 | response->status = http_statuses[0]; // Default to HTTP_OK 83 | response->header_count = 0; 84 | response->body = NULL; 85 | response->body_length = 0; 86 | } 87 | } 88 | 89 | void cleanup_http_response(http_response *response) 90 | { 91 | if (response) 92 | { 93 | if (response->version) 94 | { 95 | free((void *)response->version); 96 | response->version = NULL; 97 | } 98 | for (int i = 0; i < response->header_count; i++) 99 | { 100 | if (response->headers[i].name) 101 | { 102 | free((void *)response->headers[i].name); 103 | response->headers[i].name = NULL; 104 | } 105 | if (response->headers[i].value) 106 | { 107 | free((void *)response->headers[i].value); 108 | response->headers[i].value = NULL; 109 | } 110 | } 111 | response->header_count = 0; 112 | if (response->body) 113 | { 114 | free(response->body); 115 | response->body = NULL; 116 | response->body_length = 0; 117 | } 118 | } 119 | } 120 | 121 | void set_http_status(http_response *response, http_status_code code) 122 | { 123 | assert(response); 124 | response->status.code = code; 125 | response->status.reason = find_http_status(code)->reason; 126 | } 127 | 128 | const http_status_entry *find_http_status(http_status_code code) 129 | { 130 | for (size_t i = 0; i < HTTP_STATUS_COUNT; i++) 131 | if (http_statuses[i].code == code) 132 | return &http_statuses[i]; 133 | return NULL; 134 | } 135 | 136 | int add_http_header(http_response *response, const char *name, const char *value) 137 | { 138 | if (response && response->header_count < HTTP_MAX_HEADERS) 139 | { 140 | response->headers[response->header_count].name = strdup(name); 141 | response->headers[response->header_count].value = strdup(value); 142 | if (response->headers[response->header_count].name && response->headers[response->header_count].value) 143 | { 144 | response->header_count++; 145 | return 0; 146 | } 147 | else 148 | { 149 | fprintf(stderr, "Error: Unable to allocate memory for header\n"); 150 | return -1; 151 | } 152 | } 153 | fprintf(stderr, "Error: Exceeded maximum headers or null response\n"); 154 | return -1; 155 | } 156 | 157 | void set_http_body(http_response *response, const char *body, size_t length) 158 | { 159 | if (response) 160 | { 161 | if (response->body) 162 | { 163 | free(response->body); 164 | response->body = NULL; 165 | response->body_length = 0; 166 | } 167 | response->body = body ? strndup(body, length) : NULL; 168 | if (body) 169 | { 170 | response->body_length = length; 171 | } 172 | else 173 | { 174 | response->body_length = 0; 175 | } 176 | 177 | // Automatically set Content-Length header 178 | char content_length[20]; 179 | 180 | snprintf(content_length, sizeof(content_length), "%zu", length); 181 | // Remove the old Content-Length header if it exists 182 | // assumes that the Content-Length header is unique and only appears once 183 | int found = -1; 184 | for (int i = 0; i < response->header_count; i++) 185 | { 186 | if (strcasecmp(response->headers[i].name, "Content-Length") == 0) 187 | { 188 | found = i; 189 | break; 190 | } 191 | } 192 | if (found != -1) 193 | { 194 | free((void *)response->headers[found].value); 195 | response->headers[found].value = strdup(content_length); 196 | if (!response->headers[found].value) 197 | fprintf(stderr, "Error: Unable to allocate memory for header value\n"); 198 | } 199 | else 200 | { 201 | add_http_header(response, "Content-Length", content_length); 202 | } 203 | } 204 | } 205 | 206 | char *format_http_response(const http_response *response) 207 | { 208 | if (!response) 209 | return NULL; 210 | 211 | // estimate the size needed 212 | size_t size = 0; 213 | size += strlen(response->version) + 1; // version + space + null terminator 214 | size += MAX_STATUS_LEN; // status code and reason 215 | size += 2; // CRLF 216 | 217 | for (int i = 0; i < response->header_count; i++) 218 | { 219 | size += strlen(response->headers[i].name) + 2; // header name + colon + space 220 | size += strlen(response->headers[i].value) + 2; // header value + CRLF 221 | } 222 | 223 | size += 2; // CRLF after headers 224 | 225 | size += response->body_length; 226 | 227 | if (size > HTTP_MAX_RESPONSE_SIZE) 228 | { 229 | fprintf(stderr, "Error: Response size exceeds maximum size\n"); 230 | return NULL; 231 | } 232 | 233 | char *response_str = malloc(size + 1); // +1 for null terminator 234 | if (!response_str) 235 | { 236 | fprintf(stderr, "Error: Unable to allocate memory for response\n"); 237 | return NULL; 238 | } 239 | 240 | // initialize the response string 241 | response_str[0] = '\0'; 242 | 243 | // start formatting 244 | char status_line[MAX_STATUS_LEN]; 245 | snprintf(status_line, sizeof(status_line), "%s %d %s\r\n", response->version, response->status.code, 246 | response->status.reason); 247 | strncat(response_str, status_line, size + 1 - strlen(response_str)); // +1 for null terminator 248 | 249 | for (int i = 0; i < response->header_count; i++) 250 | { 251 | char header_line[HTTP_HEADER_NAME_MAX_LEN + HTTP_HEADER_VALUE_MAX_LEN + 4]; // 2 for colon and space, 2 for CRLF 252 | snprintf(header_line, sizeof(header_line), "%s: %s\r\n", response->headers[i].name, response->headers[i].value); 253 | strncat(response_str, header_line, size + 1 - strlen(response_str)); 254 | } 255 | 256 | strncat(response_str, "\r\n", size + 1 - strlen(response_str)); // end of headers 257 | 258 | if (response->body && response->body_length > 0) 259 | strncat(response_str, response->body, size + 1 - strlen(response_str)); 260 | 261 | return response_str; 262 | } 263 | 264 | static void set_http_headers(http_response *response, const char *headers) 265 | { 266 | if (response && headers) 267 | { 268 | char *header = strdup(headers); 269 | char *header_line = strtok(header, "\r\n"); 270 | while (header_line) 271 | { 272 | char *name = strtok(header_line, ":"); 273 | char *value = strtok(NULL, ":"); 274 | if (name && value) 275 | { 276 | add_http_header(response, name, value); 277 | } 278 | header_line = strtok(NULL, "\r\n"); 279 | } 280 | free(header); 281 | } 282 | } -------------------------------------------------------------------------------- /response.h: -------------------------------------------------------------------------------- 1 | #ifndef RESPONSE_H 2 | #define RESPONSE_H 3 | 4 | #include "my_http.h" 5 | #include // size_t 6 | 7 | typedef enum 8 | { 9 | HTTP_OK = 200, 10 | HTTP_NOT_FOUND = 404, 11 | HTTP_INTERNAL_SERVER_ERROR = 500 12 | } http_status_code; 13 | 14 | typedef struct 15 | { 16 | http_status_code code; 17 | const char *reason; 18 | } http_status_entry; 19 | 20 | typedef struct 21 | { 22 | const char *name; 23 | const char *value; 24 | } http_header; 25 | 26 | typedef struct 27 | { 28 | const char *version; 29 | http_status_entry status; 30 | http_header headers[HTTP_MAX_HEADERS]; 31 | int header_count; 32 | char *body; 33 | size_t body_length; 34 | } http_response; 35 | 36 | extern const http_status_entry http_statuses[HTTP_STATUS_COUNT]; 37 | 38 | /** 39 | * Builds an HTTP response string. 40 | * 41 | * @param version HTTP version string (e.g., "HTTP/1.1"). 42 | * @param code HTTP status code. 43 | * @param body Pointer to the body data. 44 | * @param length Length of the body data. 45 | * @return Pointer to a newly allocated string containing the raw HTTP response. 46 | * The caller is responsible for freeing this memory. 47 | */ 48 | char *build_http_response(const char *version, http_status_code code, const char *body, size_t length, const char *headers); 49 | 50 | /** 51 | * Initializes an http_response structure. 52 | * 53 | * @param response Pointer to the http_response structure to initialize. 54 | * @param version HTTP version string (e.g., "HTTP/1.1"). 55 | */ 56 | void init_http_response(http_response *response, const char *version); 57 | 58 | /** 59 | * Cleans up an http_response structure, freeing any allocated memory. 60 | * 61 | * @param response Pointer to the http_response structure to clean up. 62 | */ 63 | void cleanup_http_response(http_response *response); 64 | 65 | /** 66 | * Sets the HTTP status for the response. 67 | * 68 | * @param response Pointer to the http_response structure. 69 | * @param code The HTTP status code to set. 70 | */ 71 | void set_http_status (http_response *response, http_status_code code); 72 | 73 | /** 74 | * Finds an HTTP status entry based on the status code. 75 | * 76 | * @param code The HTTP status code to find. 77 | * @return Pointer to the corresponding http_status_entry, or NULL if not found. 78 | */ 79 | const http_status_entry *find_http_status(http_status_code code); 80 | 81 | /** 82 | * Adds a header to the HTTP response. 83 | * 84 | * @param response Pointer to the http_response structure. 85 | * @param name Name of the header. 86 | * @param value Value of the header. 87 | * @return 0 on success, non-zero on failure (e.g., if maximum headers exceeded). 88 | */ 89 | int add_http_header (http_response *response, const char *name, const char *value); 90 | 91 | /** 92 | * Sets the body of the HTTP response. 93 | * 94 | * @param response Pointer to the http_response structure. 95 | * @param body Pointer to the body data. 96 | * @param length Length of the body data. 97 | */ 98 | void set_http_body(http_response *response, const char *body, size_t length); 99 | 100 | /** 101 | * Formats the http_response structure into a raw HTTP response string. 102 | * 103 | * @param response Pointer to the http_response structure. 104 | * @return Pointer to a newly allocated string containing the raw HTTP response. 105 | * The caller is responsible for freeing this memory. 106 | */ 107 | char *format_http_response(const http_response *response); 108 | 109 | /** 110 | * Finds an HTTP status code in the http_statuses array. 111 | * 112 | * @param code The HTTP status code to find. 113 | * @return Pointer to the corresponding http_status_entry, or NULL if not found. 114 | */ 115 | const http_status_entry *find_http_status(http_status_code code); 116 | 117 | #endif // RESPONSE_H -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "http_handler.h" 13 | #include "my_socket.h" 14 | #include "request.h" 15 | #include "response.h" 16 | 17 | #define MAX_REQUEST_SIZE 2048 18 | #define MAX_CLIENTS 10 19 | #define POLL_TIMEOUT 5000 // 5 seconds 20 | 21 | char *wait_for_client_request(int client_fd, size_t *nrecv); 22 | int handle_new_connection(int server_fd, struct pollfd *fds, int *nfds); 23 | void handle_client_request(struct pollfd *pfd); 24 | 25 | static void print_http_request(http_request *request) 26 | { 27 | printf("Method: %s\n", http_methods[request->request_line.method].name); 28 | printf("URI: %s\n", request->request_line.uri); 29 | printf("Version: %s\n", request->request_line.version); 30 | printf("Header count: %d\n", request->header_count); 31 | for (int i = 0; i < request->header_count; i++) 32 | { 33 | printf("Header %d: %s: %s\n", i, request->headers[i].name, request->headers[i].value); 34 | } 35 | printf("Body: %s\n", request->body); 36 | } 37 | 38 | static void initialize_server(void); 39 | static int setup_server(void); 40 | static void run_server_loop(int server_fd, struct pollfd *fds, int *nfds); 41 | static void cleanup_server(int server_fd); 42 | 43 | int main(int argc, char *argv[]) 44 | { 45 | initialize_server(); 46 | 47 | int server_fd = setup_server(); 48 | if (server_fd == -1) 49 | return 1; 50 | 51 | struct pollfd fds[MAX_CLIENTS + 1]; 52 | int nfds = 1; 53 | 54 | fds[0].fd = server_fd; 55 | fds[0].events = POLLIN; 56 | 57 | run_server_loop(server_fd, fds, &nfds); 58 | 59 | cleanup_server(server_fd); 60 | return 0; 61 | } 62 | 63 | static void initialize_server(void) 64 | { 65 | setbuf(stdout, NULL); 66 | setbuf(stderr, NULL); 67 | } 68 | 69 | static int setup_server(void) 70 | { 71 | return init_server(); 72 | } 73 | 74 | static void run_server_loop(int server_fd, struct pollfd *fds, int *nfds) 75 | { 76 | while (1) 77 | { 78 | int poll_count = poll(fds, *nfds, POLL_TIMEOUT); 79 | 80 | if (poll_count == -1) 81 | { 82 | perror("poll"); 83 | break; 84 | } 85 | 86 | if (poll_count == 0) 87 | continue; 88 | 89 | for (int i = 0; i < *nfds; i++) 90 | { 91 | if (fds[i].revents & POLLIN) 92 | { 93 | if (fds[i].fd == server_fd) 94 | { 95 | if (handle_new_connection(server_fd, fds, nfds) == -1) 96 | return; 97 | } 98 | else 99 | { 100 | handle_client_request(&fds[i]); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | static void cleanup_server(int server_fd) 108 | { 109 | close(server_fd); 110 | } 111 | 112 | int handle_new_connection(int server_fd, struct pollfd *fds, int *nfds) 113 | { 114 | int client_fd = accept_client(server_fd); 115 | if (client_fd == -1) 116 | return -1; 117 | 118 | if (*nfds < MAX_CLIENTS + 1) 119 | { 120 | fds[*nfds].fd = client_fd; 121 | fds[*nfds].events = POLLIN; 122 | (*nfds)++; 123 | } 124 | else 125 | { 126 | fprintf(stderr, "Too many clients. Connection rejected.\n"); 127 | close(client_fd); 128 | } 129 | 130 | return 0; 131 | } 132 | 133 | void handle_client_request(struct pollfd *pfd) 134 | { 135 | http_request request; 136 | size_t nrecv = 0; 137 | char *client_request = wait_for_client_request(pfd->fd, &nrecv); 138 | 139 | if (client_request == NULL) 140 | { 141 | close(pfd->fd); 142 | pfd->fd = -1; 143 | return; 144 | } 145 | 146 | if (parse_http_request(client_request, nrecv, &request) == -1) 147 | { 148 | free(client_request); 149 | close(pfd->fd); 150 | pfd->fd = -1; 151 | return; 152 | } 153 | 154 | #ifdef DEBUG 155 | print_http_request(&request); 156 | #endif // DEBUG 157 | 158 | handle_request(&request, pfd->fd); 159 | free(client_request); 160 | } 161 | 162 | char *wait_for_client_request(int client_fd, size_t *nrecv) 163 | { 164 | char *buffer = (char *)malloc(MAX_REQUEST_SIZE); 165 | if (!buffer) 166 | { 167 | fprintf(stderr, "Failed to allocate memory for client request buffer.\n"); 168 | return NULL; 169 | } 170 | 171 | ssize_t n = recv(client_fd, buffer, MAX_REQUEST_SIZE, 0); 172 | if (n == -1) 173 | { 174 | if (errno == ECONNRESET) 175 | { 176 | fprintf(stderr, "Connection reset by peer.\n"); 177 | } 178 | else 179 | { 180 | perror("Failed to receive data from client"); 181 | } 182 | free(buffer); 183 | return NULL; 184 | } 185 | 186 | if (n == 0) 187 | { 188 | fprintf(stderr, "Client closed the connection.\n"); 189 | free(buffer); 190 | return NULL; 191 | } 192 | 193 | *nrecv = n; 194 | return buffer; 195 | } 196 | --------------------------------------------------------------------------------