diff options
author | scratko <m@scratko.xyz> | 2024-07-17 02:33:49 +0300 |
---|---|---|
committer | scratko <m@scratko.xyz> | 2024-07-17 02:33:49 +0300 |
commit | 5d4ea2faee069e94468e14232d09b774903285f1 (patch) | |
tree | fcf6fa983c84d526b725c8f94a813d01aa76aa1a | |
download | http-server-5d4ea2faee069e94468e14232d09b774903285f1.tar.gz http-server-5d4ea2faee069e94468e14232d09b774903285f1.tar.bz2 http-server-5d4ea2faee069e94468e14232d09b774903285f1.zip |
Initial commit
-rw-r--r-- | http_server.c | 580 | ||||
-rw-r--r-- | test_files/400.html | 7 | ||||
-rw-r--r-- | test_files/404.html | 7 | ||||
-rw-r--r-- | test_files/index.html | 17 |
4 files changed, 611 insertions, 0 deletions
diff --git a/http_server.c b/http_server.c new file mode 100644 index 0000000..e769c09 --- /dev/null +++ b/http_server.c @@ -0,0 +1,580 @@ +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <syslog.h> + + +#ifndef INBUFSIZE +#define INBUFSIZE 4096 +#endif + +#ifndef OUTBUFSIZE +#define OUTBUFSIZE 4096 +#endif + +#ifndef LISTEN_QLEN +#define LISTEN_QLEN 32 +#endif + +#ifndef INIT_SESS_ARR_SIZE +#define INIT_SESS_ARR_SIZE 32 +#endif + +#ifndef FILENAME_SIZE +#define FILENAME_SIZE 1024 +#endif + +enum fsm_states { + header_start, + waiting_host, + empty_line, + writing_header, + writing_body +}; + +struct session { + int fd; + unsigned long from_ip; + unsigned short from_port; + char in_buffer[INBUFSIZE]; + char out_buffer[OUTBUFSIZE]; + int in_buf_used; + int out_buf_used; + enum fsm_states state; + int rs_fd; + int data_transfer_status; + char *path; + int bad_request; + int not_found; +}; + +struct server { + int listen_socket; + struct session **session_array; + int session_size; + char *host; + char *resource_dir; +}; + +static void init_session(struct session *new_session, struct sockaddr_in *from, + int fd) +{ + new_session->fd = fd; + new_session->from_ip = ntohl(from->sin_addr.s_addr); + new_session->from_port = ntohs(from->sin_port); + new_session->in_buf_used = 0; + new_session->out_buf_used = 0; + new_session->state = header_start; + new_session->rs_fd = -1; + new_session->data_transfer_status = 0; + new_session->path = NULL; + new_session->bad_request = 0; + new_session->not_found = 0; +} + +static int accept_client(struct server *serv) +{ + int fd, i, flags; + struct sockaddr_in addr; + struct session *new_session; + socklen_t len = sizeof(addr); + fd = accept(serv->listen_socket, (struct sockaddr*) &addr, &len); + if(fd == -1) + return 0; + /* setting non-blocking mode for socket*/ + flags = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + if(fd >= serv->session_size) { + int newlen = serv->session_size; + while(fd >= newlen) + newlen += INIT_SESS_ARR_SIZE; + serv->session_array = + realloc(serv->session_array, newlen * sizeof(struct session*)); + for(i = serv->session_size; i < newlen; i++) + serv->session_array[i] = NULL; + serv->session_size = newlen; + } + new_session = malloc(sizeof(struct session)); + serv->session_array[fd] = new_session; + init_session(new_session, &addr, fd); + return 1; +} + +static int host_search_condition(const char *line) +{ + return !(strncmp(line, "Host:", strlen("Host:")) && + strncmp(line, "host:", strlen("host:"))); +} + +static int match_hosts(struct server *serv, const char *line, int length) +{ + int i, last_idx; + + if(strlen("host:") > length) + return 0; + if(!host_search_condition(line)) + return 0; + for(i = strlen("host:"); i < length; ++i) { + if(line[i] == ' ') + continue; + break; + } + if(i+strlen(serv->host) > length) + 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) { + if(line[last_idx] == ' ') + continue; + else + break; + } + if(line[last_idx] == '\r'|| line[last_idx] == '\n') + return 1; + return 0; +} + +static int match_protocols(const char *line, int length) +{ + int i; + /* skip extract path */ + for(i = 4; i < length && line[i] != ' '; ++i) + {} + if(i >= length) + return 0; + ++i; + if(i+strlen("HTTP/1.1") > length) + return 0; + if(strncmp(line+i, "HTTP/1.1", strlen("HTTP/1.1"))) + return 0; + for(i += strlen("HTTP/1.1"); i < length; ++i) { + if(line[i] == ' ') + continue; + if(line[i] == '\r' || line[i] == '\n') + return 1; + return 0; + } + return 0; +} + +static char* extract_path(const char *line, int length) +{ + int i; + char *path; + + for(i = 4; i < length && line[i] != ' '; ++i) + {} + if(i >= length) + return NULL; + + length = i-4+1 ; + path = malloc(length); + memcpy(path, line+4, length-1); + path[length-1] = '\0'; + return path; +} + +static int command_processing(struct server *serv, struct session *cur_session, + const char *line, int length) +{ + switch(cur_session->state) { + case header_start: + if(length > strlen("GET") && !strncmp(line, "GET", strlen("GET"))) { + cur_session->path = extract_path(line, length); + if(cur_session->path == NULL) + return 0; + if(!match_protocols(line, length)) + return 0; + cur_session->state = waiting_host; + return 1; + } + case waiting_host: + if(!match_hosts(serv, line, length)) + return 0; + cur_session->state = empty_line; + return 1; + case empty_line: + if(line == NULL) { + cur_session->data_transfer_status = 1; + cur_session->state = writing_header; + return 1; + } + default: + return 0; + } +} + +/* + * 0 - close current session + * 1 - continue + */ +static int read_in_session(struct server *serv, struct session *cur_session) +{ + int read_res, i, end_idx; + char *line = NULL; + end_idx = -1; + + if(!cur_session->in_buf_used) + read_res = read(cur_session->fd, cur_session->in_buffer, INBUFSIZE); + if(read_res <= 0) + return 0; + + cur_session->in_buf_used += read_res; + + do { + for(i = 0; i < cur_session->in_buf_used; ++i) { + if(cur_session->in_buffer[i] == '\n') { + end_idx = i; + break; + } + } + if(end_idx == -1) { + if(cur_session->in_buf_used == INBUFSIZE) + return 0; + return 1; + } + if(cur_session->state != empty_line) { + line = malloc(end_idx+1); + memcpy(line, cur_session->in_buffer, end_idx+1); + } + cur_session->in_buf_used -= i+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)) + continue; + else if(cur_session->state == empty_line && + !strncmp(cur_session->in_buffer, "\r\n", 2)) + cur_session->in_buf_used = 0; + + if(!command_processing(serv, cur_session, line, end_idx)) { + cur_session->bad_request = 1; + cur_session->data_transfer_status = 1; + cur_session->state = writing_header; + } + if(line) { + free(line); + line = NULL; + } + if(cur_session->bad_request) + return 1; + } while(cur_session->in_buf_used); + return 1; +} + +static void print_header(struct session *cur_session, char *status) +{ + write(cur_session->fd, status, strlen(status)); +} + +static int is_index_page(char *path) +{ + int i; + for(i = strlen(path)-1; i >= 0 && path[i] != '/'; --i) { + if(path[i] == '.') + return 0; + } + return 1; +} + +static void remove_slash(char *root) +{ + int last_idx = strlen(root); + --last_idx; + if(root[last_idx] == '/') + root[last_idx] = '\0'; +} + +static int open_file(char *root, struct session *cur_session) +{ + int i, j; + int add_index_page = 0; + char *given_path = cur_session->path; + char *str_index_page = "index.html"; + char new_path[FILENAME_SIZE]; + + remove_slash(root); + strncpy(new_path, root, strlen(root)); + add_index_page = is_index_page(given_path) ? 1 : 0; + for(i = strlen(root), j = 0; i < FILENAME_SIZE && + j < strlen(given_path); ++i, ++j) + new_path[i] = given_path[j]; + if(add_index_page) { + for(j = 0; i < FILENAME_SIZE && j < strlen(str_index_page); ++i, ++j) + new_path[i] = str_index_page[j]; + } + if(i < FILENAME_SIZE) + new_path[i] = '\0'; + cur_session->rs_fd = open(new_path, O_RDONLY); + if(cur_session->rs_fd == -1) + return 0; + else return 1; +} + +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); + *out_buf_used -= writed_size; +} + +static void write_error_page(struct session *cur_session) +{ + int read_result; + + read_result = read(cur_session->rs_fd, cur_session->out_buffer, + OUTBUFSIZE); + write(cur_session->fd, cur_session->out_buffer, read_result); +} + +static int file_open_condition(struct session *cur_session) +{ + return !cur_session->bad_request && !cur_session->not_found && + cur_session->rs_fd == -1; +} + +static char* get_page_error_path(struct server *serv, char *page) +{ + char *new_path; + + new_path = malloc(strlen(serv->resource_dir) + strlen(page)); + strcpy(new_path, serv->resource_dir); + strcat(new_path, page); + return new_path; +} + +static int write_in_session(struct server *serv, struct session *cur_session) +{ + int read_result, write_result, open_result; + char *page_error_path = NULL; + + if(file_open_condition(cur_session)) { + open_result = open_file(serv->resource_dir, cur_session); + if(!open_result) { + cur_session->state = writing_header; + cur_session->not_found = 1; + } + } + switch(cur_session->state) { + case writing_header: + if(cur_session->bad_request) + print_header(cur_session, "HTTP/1.1 400 Bad Request\n\n"); + else if(cur_session->not_found) + print_header(cur_session, "HTTP/1.1 404 Not Found\n\n"); + else + print_header(cur_session, "HTTP/1.1 200 OK\n\n"); + cur_session->state = writing_body; + return 1; + case writing_body: + /* ---------- error page write ---------- */ + if(cur_session->bad_request) + page_error_path = get_page_error_path(serv, "/400.html"); + else if(cur_session->not_found) + page_error_path = get_page_error_path(serv, "/404.html"); + + if(cur_session->bad_request || cur_session->not_found) { + cur_session->rs_fd = open(page_error_path, O_RDONLY); + if(cur_session->rs_fd == -1) + syslog(LOG_ERR, "can't find error page"); + free(page_error_path); + write_error_page(cur_session); + return 0; + } + /* ---------- normal page write ---------- */ + + /* 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); + 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; + } + write_result = write(cur_session->fd, cur_session->out_buffer, + cur_session->out_buf_used); + + /* writed remaining some of data */ + if(write_result < cur_session->out_buf_used) + change_out_buffer(cur_session->out_buffer, + &cur_session->out_buf_used, + write_result); + /* writed all data from out_buffer */ + else + cur_session->out_buf_used = 0; + return 1; + default: + return 0; + } +} + +static void close_session(struct server *serv, int fd) +{ + 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) + close(serv->session_array[fd]->rs_fd); + serv->session_array[fd] = NULL; +} + +static int reading_condition(struct session *cur_session) +{ + return cur_session && !cur_session->data_transfer_status; +} + +static int writing_condition(struct session *cur_session) +{ + return cur_session && cur_session->data_transfer_status; +} + +int main_loop(struct server *serv) +{ + int i, maxfd, select_result, accept_result, read_result, write_result; + fd_set readfds, writefds; + + for(;;) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(serv->listen_socket, &readfds); + maxfd = serv->listen_socket; + for(i = 0; i < serv->session_size; ++i) { + if(reading_condition(serv->session_array[i])) { + FD_SET(i, &readfds); + if(i > maxfd) + maxfd = i; + } + if(writing_condition(serv->session_array[i])) { + FD_SET(i, &writefds); + if(i > maxfd) + maxfd = i; + } + } + + select_result = select(maxfd + 1, &readfds, &writefds, NULL, NULL); + if(!select_result) { + syslog(LOG_ERR, "select error"); + return 3; + } + + if(FD_ISSET(serv->listen_socket, &readfds)) { + accept_result = accept_client(serv); + if(!accept_result) { + syslog(LOG_ERR, "accept error"); + return 4; + } + } + + for(i = 0; i < serv->session_size; ++i) { + if(serv->session_array[i] && FD_ISSET(i, &readfds)) { + 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); + } + if(serv->session_array[i] && FD_ISSET(i, &writefds)) { + write_result = write_in_session(serv, serv->session_array[i]);; + /* + * Closing the connection if all data is written or + * error 400 or 404 occurs + */ + if(!write_result) + close_session(serv, i); + } + } + } +} + +/* + * 0 - error + * 1 - success + */ +static int init_server(struct server *serv, int port, char *host, + char *resource_dir) +{ + int opt, i; + struct sockaddr_in addr; + + serv->host = host; + serv->resource_dir = resource_dir; + + serv->listen_socket = socket(AF_INET, SOCK_STREAM, 0); + if(serv->listen_socket == -1) + return 0; + + opt = 1; + setsockopt(serv->listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if(bind(serv->listen_socket, (struct sockaddr*) &addr, sizeof(addr)) == -1) + return 0; + + serv->session_array = malloc(sizeof(serv->session_array) * + INIT_SESS_ARR_SIZE); + serv->session_size = INIT_SESS_ARR_SIZE; + for(i = 0; i < INIT_SESS_ARR_SIZE; i++) + serv->session_array[i] = NULL; + + listen(serv->listen_socket, LISTEN_QLEN); + return 1; +} + +static void demonization() +{ + int pid; + + close(0); + close(1); + close(2); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); + chdir("/"); + pid = fork(); + if(pid) + exit(0); + setsid(); + pid = fork(); + if(pid) + exit(0); +} + +int main(int argc, char **argv) +{ + unsigned short port; + struct server serv; + + if(argc != 4) { + fprintf(stderr, + "Usage: <port> <host> <full_path/to/host_files\n"); + return 1; + } + demonization(); + openlog("scratko's http server", 0, LOG_USER); + syslog(LOG_INFO, "daemon started"); + port = strtol(argv[1], NULL, 10); + + if(!init_server(&serv, port, argv[2], argv[3])) { + syslog(LOG_ERR, "server initialization error"); + return 2; + } + return main_loop(&serv); + syslog(LOG_INFO, "server terminated"); +} diff --git a/test_files/400.html b/test_files/400.html new file mode 100644 index 0000000..edfac25 --- /dev/null +++ b/test_files/400.html @@ -0,0 +1,7 @@ +<html> +<head><title>400 Bad Request</title></head> +<body> +<center><h1>400 Bad Request</h1></center> +<hr><center>scratko's server 1.0</center> +</body> +</html> diff --git a/test_files/404.html b/test_files/404.html new file mode 100644 index 0000000..e8286d3 --- /dev/null +++ b/test_files/404.html @@ -0,0 +1,7 @@ +<html> +<head><title>404 Not Found</title></head> +<body> +<center><h1>404 Not Found</h1></center> +<hr><center>scratko's server 1.0</center> +</body> +</html> diff --git a/test_files/index.html b/test_files/index.html new file mode 100644 index 0000000..1dfbbed --- /dev/null +++ b/test_files/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<title>Welcome to nginx!</title> +<style> + body { + width: 35em; + margin: 0 auto; + font-family: Tahoma, Verdana, Arial, sans-serif; + } +</style> +</head> +<body> +<h1>Welcome to scratko's server!</h1> +<p>Test page.</p> +</body> +</html> |