├── .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 |
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 |
17 |
18 |
19 |
20 |
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 |
--------------------------------------------------------------------------------