back to scratko.xyz
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md86
-rw-r--r--http_server.c93
-rw-r--r--http_server.pngbin0 -> 23866 bytes
-rw-r--r--test_files/index.html2
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
new file mode 100644
index 0000000..a4c7092
--- /dev/null
+++ b/http_server.png
Binary files differ
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;