Skip to content

aroch17/http-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 

Repository files navigation

HTTP Server in C

Are you even a systems engineer if you haven't built an HTTP server in C? This one's mine. I accidently discovered path traversal vulnerabilities through this project! Read more about it here: https://www.ayushr.dev/posts/path-traversal-vulnerability

Features

Constant Time Polling

This server uses non-blocking I/O to allow for constant time polling using kqueue. This allows the server to support concurrent connections without the need for separate threads and O(N) time waiting.

int kq = kqueue();
struct kevent event, events[MAX_EVENTS];

EV_SET(&event, serverfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event, 1, NULL, 0, NULL);

while (1) {
  int nevn = kevent(kq, NULL, 0, events, MAX_EVENTS, NULL);
  if (nevn == -1) {
    perror("kevent wait");
    break;
  }

  for (int i = 0; i < nevn; i++) {
    if (events[i].ident == serverfd) {
      // accept new connection
      
      EV_SET(&event, clientfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
      kevent(kq, &event, 1, NULL, 0, NULL);
      printf("Client connected!\n");
    }
    // existing connections
    else {
      // service client

      // close connection
      struct kevent event;
      EV_SET(&event, events[i].ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
      kevent(kq, &event, 1, NULL, 0, NULL);
      close(clientfd);
    }
  }
}

Persistent Sends

Since network traffic can be unpredicatable and messages can be dropped, messages are sent until either the complete message gets sent or an error occurs. EINTR interrupts are ignored.

/*
Sends message persistently - packet drop resistant

Input: sockfd - receiver
			 msg - message to send
			 len - length of msg
			 flags - options

Output: null
*/
void send_message(int sockfd, char* msg, int len, int flags) {
	int bytes_sent, total = 0;
	while (total != len) {
		bytes_sent = send(sockfd, msg + total, len - total, 0);
		if (bytes_sent == -1 && errno == EINTR) {
			continue;
		}
		if (bytes_sent == -1) {
			printf("send error: %s\n", strerror(errno));
			exit(1);
		}
		total += bytes_sent;
	}
}

File Streaming

When files are bigger than the buffer size (1024 by default), files are split into chunks of 1024 bytes and streamed over multiple packets.

// Stream the file
long total_bytes_read = 0;
while (total_bytes_read < size) {
  long read_size = size - total_bytes_read > BUF_SIZE ? BUF_SIZE : size - total_bytes_read;
  size_t bytes_read = fread(file_buf, 1, read_size, f);
  if (bytes_read == 0) {
    if (feof(f)) {
      break;
    }
    else {
      printf("fread error: error reading file.\n");
      fclose(f);
      exit(1);
    }
  }
  send_message(clientfd, file_buf, bytes_read, 0);
  total_bytes_read += bytes_read;
}
fclose(f);

Start server

$ gcc -Wall -Werror -o main main.c
$ ./main

Accessible Endpoints

Home - GET /

Responds with 200 OK with an empty body

Echo - GET /echo/{str}

Responds with 200 OK with str as the body

Header Parsing - GET /user-agent

Responds with 200 OK with the value of the user-agent header as the body

Note: If you are using curl, you can use the --header flag to supply custom header values

Query Files - GET /files/{file}

Responds with 200 OK if the file exists in the files directory, and returns a response with the Content-Type and Content-Length headers and the file contents as the body Responds with 404 Not Found if file doesn't exist

Add/Edit Files - POST /files/{file}

Responds with 200 OK and adds the file to the files directory Responds with 405 Not Allowed if any other method is used or the filepath is not in files directory

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published