diff options
| -rw-r--r-- | README.md | 86 | ||||
| -rw-r--r-- | http_server.c | 93 | ||||
| -rw-r--r-- | http_server.png | bin | 0 -> 23866 bytes | |||
| -rw-r--r-- | test_files/index.html | 2 |
4 files changed, 142 insertions, 39 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..10c768d --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# Simple HTTP/1.1 Web Server + +<img src="http_server.png" /> + +## Description + +This project is a lightweight web server implemented in C, using raw TCP sockets +and the POSIX API. It serves static files from a specified resource directory +and handles multiple clients concurrently in a non-blocking fashion. + +### Features + +* Implements the HTTP/1.1 protocol (basic subset). +* Handles GET requests for static files. +* Uses non-blocking I/O with select. +* Manages multiple client sessions via a custom session array. +* Basic request parsing with a finite state machine (FSM). +* Logs events using syslog. +* Sends appropriate HTTP responses, including 400 Bad Request and 404 Not Found. + +## Technologies Used + +* C (ISO C90/C99) +* POSIX sockets API (socket, bind, accept, etc.) +* Non-blocking I/O with select +* TCP/IP Networking +* Syslog logging + +## Structure + +`struct server`: represents the server and holds the listening socket and +session array. `struct session`: represents a connected client and holds state +and buffers. Custom FSM (`enum fsm_states`) for request parsing and response +generation. Defines for buffer sizes and constants allow easy tuning. + +## Usage + +Compile with: + +``` +git clone https://git.scratko.xyz/http-server +cd http-server +gcc -Wall -O2 http_server.c -o http_server +``` + +Run: +``` +./http_server [port] [host] [resource_directory] +``` + +Example: +``` +./http_server 8082 test.scratko.xyz:8082 /var/www/test +``` + +## Notes + +Only basic HTTP/1.1 functionality is supported (no keep-alive, POST, etc.). +Designed for educational and experimental purposes. +Ensure the resource directory and files have proper read permissions. + +The http-server directory contains a ``test_files`` subdirectory with the files: +`400.html`, `404.html`, and `index.html`. These are placeholder pages that can +be placed in the resource directory specified when launching the server +(`[resource_directory]`). If the server starts correctly, you should see +`index.html` in your browser (as shown in the screenshot). + +If the browser sends an incorrect GET request, or if you specify an incorrect +host when launching the server — for example: + +``` +server 8082 test.scrato.xyz:8082 /var/www/test +``` + +Note the intentional typo in the hostname (scrato instead of scratko). Then, +when the browser accesses test.scratko.xyz:8082, it will receive the `400.html` +page (Bad Request). + +Another case: if you try to access a nonexistent section or file of the site, +for example: + +``` +test.scratko.xyz:8082/test +``` + +you will receive the `404.html` page (Page Not Found). diff --git a/http_server.c b/http_server.c index e769c09..84f35a9 100644 --- a/http_server.c +++ b/http_server.c @@ -6,6 +6,7 @@ #include <string.h> #include <fcntl.h> #include <syslog.h> +#include <sys/resource.h> #ifndef INBUFSIZE @@ -111,24 +112,24 @@ static int host_search_condition(const char *line) strncmp(line, "host:", strlen("host:"))); } -static int match_hosts(struct server *serv, const char *line, int length) +static int match_hosts(struct server *serv, const char *line, int end_idx) { int i, last_idx; - if(strlen("host:") > length) + if(strlen("host:") > end_idx) return 0; if(!host_search_condition(line)) return 0; - for(i = strlen("host:"); i < length; ++i) { + for(i = strlen("host:"); i <= end_idx; ++i) { if(line[i] == ' ') continue; break; } - if(i+strlen(serv->host) > length) + if(i+strlen(serv->host) > end_idx) return 0; if(strncmp(line+i, serv->host, strlen(serv->host))) return 0; - for(last_idx = i+strlen(serv->host); last_idx < length; ++last_idx) { + for(last_idx = i+strlen(serv->host); last_idx <= end_idx; ++last_idx) { if(line[last_idx] == ' ') continue; else @@ -139,20 +140,24 @@ static int match_hosts(struct server *serv, const char *line, int length) return 0; } -static int match_protocols(const char *line, int length) +static int match_protocols(const char *line, int end_idx) { int i; - /* skip extract path */ - for(i = 4; i < length && line[i] != ' '; ++i) + /* + * skip extract path + * find the space after the path + * and then ++i + */ + for(i = 4; i <= end_idx && line[i] != ' '; ++i) {} - if(i >= length) + if(i > end_idx) return 0; ++i; - if(i+strlen("HTTP/1.1") > length) + if(i+strlen("HTTP/1.1") > end_idx) return 0; if(strncmp(line+i, "HTTP/1.1", strlen("HTTP/1.1"))) return 0; - for(i += strlen("HTTP/1.1"); i < length; ++i) { + for(i += strlen("HTTP/1.1"); i <= end_idx; ++i) { if(line[i] == ' ') continue; if(line[i] == '\r' || line[i] == '\n') @@ -162,14 +167,18 @@ static int match_protocols(const char *line, int length) return 0; } -static char* extract_path(const char *line, int length) +static char* extract_path(const char *line, int end_idx) { - int i; + int i, length; char *path; - for(i = 4; i < length && line[i] != ' '; ++i) + /* + * find space after path to calculate + * number of characters in path + */ + for(i = 4; i <= end_idx && line[i] != ' '; ++i) {} - if(i >= length) + if(i > end_idx) return NULL; length = i-4+1 ; @@ -180,21 +189,21 @@ static char* extract_path(const char *line, int length) } static int command_processing(struct server *serv, struct session *cur_session, - const char *line, int length) + const char *line, int end_idx) { switch(cur_session->state) { case header_start: - if(length > strlen("GET") && !strncmp(line, "GET", strlen("GET"))) { - cur_session->path = extract_path(line, length); + if(end_idx > strlen("GET") && !strncmp(line, "GET", strlen("GET"))) { + cur_session->path = extract_path(line, end_idx); if(cur_session->path == NULL) return 0; - if(!match_protocols(line, length)) + if(!match_protocols(line, end_idx)) return 0; cur_session->state = waiting_host; return 1; } case waiting_host: - if(!match_hosts(serv, line, length)) + if(!match_hosts(serv, line, end_idx)) return 0; cur_session->state = empty_line; return 1; @@ -215,12 +224,12 @@ static int command_processing(struct server *serv, struct session *cur_session, */ static int read_in_session(struct server *serv, struct session *cur_session) { - int read_res, i, end_idx; + int read_res, i, end_idx, bufp = cur_session->in_buf_used; char *line = NULL; end_idx = -1; - if(!cur_session->in_buf_used) - read_res = read(cur_session->fd, cur_session->in_buffer, INBUFSIZE); + read_res = read(cur_session->fd, cur_session->in_buffer + bufp, + INBUFSIZE - cur_session->in_buf_used); if(read_res <= 0) return 0; @@ -234,7 +243,7 @@ static int read_in_session(struct server *serv, struct session *cur_session) } } if(end_idx == -1) { - if(cur_session->in_buf_used == INBUFSIZE) + if(cur_session->in_buf_used >= INBUFSIZE) return 0; return 1; } @@ -242,16 +251,18 @@ static int read_in_session(struct server *serv, struct session *cur_session) line = malloc(end_idx+1); memcpy(line, cur_session->in_buffer, end_idx+1); } - cur_session->in_buf_used -= i+1; + cur_session->in_buf_used -= end_idx+1; memmove(cur_session->in_buffer, cur_session->in_buffer+end_idx+1, cur_session->in_buf_used); /* skip the remaining data in the header */ if(cur_session->state == empty_line && - strncmp(cur_session->in_buffer, "\r\n", 2)) + (strncmp(cur_session->in_buffer, "\r\n", 2) || + strncmp(cur_session->in_buffer, "\n", 1))) continue; else if(cur_session->state == empty_line && - !strncmp(cur_session->in_buffer, "\r\n", 2)) + (!strncmp(cur_session->in_buffer, "\r\n", 2) || + !strncmp(cur_session->in_buffer, "\n", 1))) cur_session->in_buf_used = 0; if(!command_processing(serv, cur_session, line, end_idx)) { @@ -321,7 +332,7 @@ static int open_file(char *root, struct session *cur_session) static void change_out_buffer(char *out_buffer, int *out_buf_used, int writed_size) { - memmove(out_buffer, out_buffer+writed_size, (*out_buffer)-writed_size); + memmove(out_buffer, out_buffer+writed_size, (*out_buf_used)-writed_size); *out_buf_used -= writed_size; } @@ -389,24 +400,25 @@ static int write_in_session(struct server *serv, struct session *cur_session) } /* ---------- normal page write ---------- */ - /* if buffer doesn't contain any data*/ + /* if buffer doesn't contain any data */ if(!cur_session->out_buf_used) { read_result = read(cur_session->rs_fd, cur_session->out_buffer, OUTBUFSIZE); /* end of file */ if(!read_result) { - close(cur_session->rs_fd); + if(cur_session->rs_fd != -1) + close(cur_session->rs_fd); cur_session->rs_fd = -1; cur_session->data_transfer_status = 0; cur_session->state = header_start; return 0; } - cur_session->out_buf_used += read_result; + cur_session->out_buf_used = read_result; } write_result = write(cur_session->fd, cur_session->out_buffer, cur_session->out_buf_used); - /* writed remaining some of data */ + /* not all data has writed */ if(write_result < cur_session->out_buf_used) change_out_buffer(cur_session->out_buffer, &cur_session->out_buf_used, @@ -422,11 +434,12 @@ static int write_in_session(struct server *serv, struct session *cur_session) static void close_session(struct server *serv, int fd) { - close(fd); + if(fd != -1) + close(fd); serv->session_array[fd]->fd = -1; if(serv->session_array[fd]->path) free(serv->session_array[fd]->path); - if(serv->session_array[fd]->rs_fd) + if(serv->session_array[fd]->rs_fd != -1) close(serv->session_array[fd]->rs_fd); serv->session_array[fd] = NULL; } @@ -465,7 +478,7 @@ int main_loop(struct server *serv) } select_result = select(maxfd + 1, &readfds, &writefds, NULL, NULL); - if(!select_result) { + if(select_result == -1) { syslog(LOG_ERR, "select error"); return 3; } @@ -483,7 +496,7 @@ int main_loop(struct server *serv) read_result = read_in_session(serv, serv->session_array[i]); /* normal connection closure (by user or full of in_buffer) */ if(!read_result) - close_session(serv, i); + close_session(serv, serv->session_array[i]->fd); } if(serv->session_array[i] && FD_ISSET(i, &writefds)) { write_result = write_in_session(serv, serv->session_array[i]);; @@ -492,7 +505,7 @@ int main_loop(struct server *serv) * error 400 or 404 occurs */ if(!write_result) - close_session(serv, i); + close_session(serv, serv->session_array[i]->fd); } } } @@ -567,7 +580,10 @@ int main(int argc, char **argv) return 1; } demonization(); - openlog("scratko's http server", 0, LOG_USER); + struct rlimit core_limits; + core_limits.rlim_cur = core_limits.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_CORE, &core_limits); + openlog("http server", 0, LOG_USER); syslog(LOG_INFO, "daemon started"); port = strtol(argv[1], NULL, 10); @@ -577,4 +593,5 @@ int main(int argc, char **argv) } return main_loop(&serv); syslog(LOG_INFO, "server terminated"); + closelog(); } diff --git a/http_server.png b/http_server.png Binary files differnew file mode 100644 index 0000000..a4c7092 --- /dev/null +++ b/http_server.png diff --git a/test_files/index.html b/test_files/index.html index 1dfbbed..f27374b 100644 --- a/test_files/index.html +++ b/test_files/index.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> -<title>Welcome to nginx!</title> +<title>Welcome to scratko's server!</title> <style> body { width: 35em; |
