Skip to content

Instantly share code, notes, and snippets.

@king1600
Created July 3, 2017 17:29
Show Gist options
  • Save king1600/56535855cfd0bb09904b025b4c20c9a5 to your computer and use it in GitHub Desktop.
Save king1600/56535855cfd0bb09904b025b4c20c9a5 to your computer and use it in GitHub Desktop.
Mini GET only HTTP Server in C
#include "http.h"
#include <netdb.h> // addrinfo, getaddrinfo, freeaddrinfo, AI_PASSIVE, gai_strerror
#include <fcntl.h> // fcntl
#include <stdio.h> // printf, fprintf, fileno
#include <stdlib.h> // calloc, free
#include <string.h> // memcpy, memset
#include <unistd.h> // write, close
#include <netinet/tcp.h> // TCP_NODELAY, TCP_CORK/TCP_NOPUSH
// use BSD style flag
#ifndef TCP_NOPUSH
#define TCP_NOPUSH TCP_CORK
#endif
/**
* Print error and close program
* @param {char*} err the error to print
*/
void CError(const char *err) {
fprintf(stderr, "[x] %s", err);
abort();
}
/**
* Parse path of http request
* @param {char*} data the http request data
* @param {HttpReq*} req the request object to save data to
*/
void HttpParse(const char* data, HttpReq* req) {
// check if request is complete
req->complete = strstr(data, "\r\n\r\n") == NULL ? 0 : 1;
// check if connection should keep alive
req->keep_alive = strstr(
data, "Connection: keep-alive\r\n") == NULL ? 0 : 1;
// extract file path from request
if (req->path == NULL || strlen(req->path) < 1) {
char *p_start = strstr(data, " ");
char *p_end = strstr(data + (p_start - data) + 1, " ");
char *q_end = strstr(data + (p_start - data) + 1, "?");
// display HTTP Method
char *m_end = strstr(data, "\r\n");
write(STDOUT_FILENO, "[*] ", 4);
write(STDOUT_FILENO, data, m_end - data);
write(STDOUT_FILENO, "\r\n", 2);
// get path
if (q_end != NULL) p_end = q_end - 1;
int size = (p_end - data) - (p_start - data) - 1;
req->path = (char*)calloc(size, sizeof(char*));
memcpy(req->path, data + (p_start - data) + 1, size);
}
}
/**
* Set a client socket blocking mode
* @param {Client*} client the client socket
* @param {int} blocking state (1 = on 0 = off)
*/
int CBlocking(Client* client, int on) {
int flags = fcntl(client->fd, F_GETFL, 0);
if (flags < 0) return -1;
flags = !on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
flags = fcntl(client->fd, F_SETFL, flags);
return flags != 0 ? -1 : 0;
}
/**
* Set TCP_NODELAY flag on client to reduce latency
* @param {Client*} client the client socket
* @param {int} flag state (1 = on 0 = off)
*/
int CNoDelay(Client* client, int on) {
int ret = setsockopt(client->fd,
IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
return ret < 0 ? -1 : 0;
}
/**
* Set TCP_NO flag on client to buildup data before sending
* @param {Client*} client the client socket
* @param {int} flag state (1 = on 0 = off)
*/
int CNoPush(Client* client, int on) {
int ret = setsockopt(client->fd,
IPPROTO_TCP, TCP_NOPUSH, &on, sizeof(on));
return ret < 0 ? -1 : 0;
}
/**
* Create a server socket using a client object
* @param {Client*} client the client to hose the server socket
* @param {char*} the port number as c-string to host server on
*/
int openServer(Client *c, const char *port) {
// find address to bind to
int ret, flag = 1;
struct addrinfo hints, *res, *r;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
ret = getaddrinfo(NULL, port, &hints, &res);
if (ret != 0) {
fprintf(stderr, "Failed to get address info: %s\n",
gai_strerror(ret));
return -1;
}
// look through address results and pick one that works
for (r = res; r != NULL; r = r->ai_next) {
c->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
if (c->fd == -1) continue;
ret = setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
if (ret < 0) { close(c->fd); continue; }
#ifdef SO_REUSEPORT
ret = setsockopt(c->fd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag));
#endif
if (ret < 0) { close(c->fd); continue; }
ret = bind(c->fd, r->ai_addr, r->ai_addrlen);
if (ret == 0) break;
close(c->fd);
}
// check if creating socket was successful
if (r == NULL || c->fd < 0) {
CError("Could not bind socket\n");
return -1;
}
freeaddrinfo(res);
return c->fd;
}
#ifndef _HTTP_H
#define _HTTP_H
/** Http Request object */
typedef struct HttpReq {
char* path; // the path url to fetch
unsigned char complete; // if ready to respond to
unsigned char keep_alive; // if connection should be kept-alive
} HttpReq;
/**
* Parse path of http request
* @param {char*} data the http request data
* @param {HttpReq*} req the request object to save data to
*/
void HttpParse(const char* data, HttpReq* req);
/** A socket client */
typedef struct Client {
int fd; // the socket file descriptor
HttpReq req; // the http object associated with it
} Client;
/**
* Print error and close program
* @param {char*} err the error to print
*/
void CError(const char *err);
/**
* Set a client socket blocking mode
* @param {Client*} client the client socket
* @param {int} blocking state (1 = on 0 = off)
*/
int CBlocking(Client* client, int on);
/**
* Set TCP_NODELAY flag on client to reduce latency
* @param {Client*} client the client socket
* @param {int} flag state (1 = on 0 = off)
*/
int CNoDelay(Client* client, int on);
/**
* Set TCP_NO flag on client to buildup data before sending
* @param {Client*} client the client socket
* @param {int} flag state (1 = on 0 = off)
*/
int CNoPush(Client* client, int on);
/**
* Create a server socket using a client object
* @param {Client*} client the client to hose the server socket
* @param {char*} the port number as c-string to host server on
*/
int openServer(Client *client, const char *port);
#endif
#include "http.h"
#include <stdio.h> // fopen, fclose, ftell, fseek, printf, sprintf, fileno
#include <errno.h> // errno
#include <string.h> // memset
#include <stdlib.h> // free, malloc, calloc
#include <unistd.h> // write, read, close
#include <sys/epoll.h> // epoll_create1, epoll_ctl, epoll_wait
#include <netinet/in.h> // sockaddr, accept
#include <sys/sendfile.h> // sendfile
#define READ_BUFFER 4096 // amount of data to read per session
#define MAX_EVENTS 64 // max simultanious epoll events in one tick
#define MAX_CONNS 1024 // max amount of backlog connections
// norrmal ile http response
static const char* NORMAL_RESP =
"HTTP/1.1 200 Ok\r\n"
"Content-Length: %d\r\n"
"Server: epoll-server\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n";
// file not found response
static const char* FILE_ERR_RESP =
"HTTP/1.1 400 Not Found\r\n"
"Content-Length: 9\r\n"
"Server: epoll-server\r\n"
"Content-Type: text/plain\r\n"
"\r\nNot Found";
// server error responses
static const char* SERVER_ERR_RESP =
"HTTP/1.1 500 Interal Server Error\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 20\r\n"
"\r\nFailed to open file!";
// run the server
int run(const char* port);
int main(int argc, char* argv[]) {
// get server port number
if (argc < 2)
CError("Provide port number\n");
// change directory if provided
if (argc > 2) {
printf("[^] Moving to: %s\n", argv[2]);
if (chdir(argv[2]) < 0)
CError("Failed to change directory\n");
}
// start event loop
return run(argv[1]);
}
/**
* Destroy a client object (close socket, free data)
* @param {Client*} sock the client object to destroy
*/
static inline void CFree(Client *sock) {
if (sock != NULL) {
close(sock->fd);
if (sock->req.path != NULL)
if (strlen(sock->req.path) > 0)
free(sock->req.path);
free(sock);
}
}
/**
* Respond to a client request
* @param {Client*} sock the request to respond to
*/
static inline void Respond(Client *sock) {
// setup socket for writing
CBlocking(sock, 1);
CNoDelay(sock, 1);
// check if file exists
if (access(sock->req.path + 1, F_OK) != -1) {
FILE *f = fopen(sock->req.path + 1, "rb");
// could not open file
if (f == NULL) {
write(sock->fd, SERVER_ERR_RESP, strlen(SERVER_ERR_RESP));
// use sendfile to send file contents
} else {
// get file size
off_t offset = 0;
fseek(f, 0, SEEK_END);
ssize_t sent = 0, size = ftell(f);
fseek(f, 0, SEEK_SET);
// send header
CNoDelay(sock, 0);
CNoPush(sock, 1);
char header[512];
sprintf(header, NORMAL_RESP, size);
write(sock->fd, header, strlen(header));
// send data
size -= 1;
int filefd = fileno(f);
while (sent < size) {
sent = sendfile(sock->fd, filefd, &offset, size - sent);
if (sent < 0) {
CFree(sock);
return;
}
}
// finish sending
CNoPush(sock, 0);
CNoDelay(sock, 1);
sent = sendfile(sock->fd, filefd, &offset, size);
fclose(f);
}
// file doesn't exit, 404
} else {
write(sock->fd, FILE_ERR_RESP, strlen(FILE_ERR_RESP));
}
// Close socket if not keep alive
if (sock->req.keep_alive != 1) {
CFree(sock);
return;
}
// Return socket back to normal
CBlocking(sock, 0);
sock->req.complete = 0;
if (strlen(sock->req.path) > 0)
free(sock->req.path);
}
/**
* Start running the http server
* @param {char*} port the port to start it on
*/
int run(const char* port) {
Client server; // the server object
ssize_t nread; // amount read from client
char buffer[READ_BUFFER]; // data read from client
struct epoll_event event; // creating epoll events
struct epoll_event *events; // events polled
int i, ret, epoll_fd, polled; // epoll and other vars
// create server socket
if (openServer(&server, port) < 0)
return -1;
if (CBlocking(&server, 0) < 0)
CError("Failed to set server non-blocking\n");
if (listen(server.fd, MAX_CONNS) < 0)
CError("Failed to set listen backlog\n");
// create epoll context and register server
epoll_fd = epoll_create1(0);
if (epoll_fd < 0)
CError("Failed to create epoll fd\n");
event.data.fd = server.fd;
event.events = EPOLLIN | EPOLLET;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server.fd, &event);
if (ret < 0) CError("Server epoll_ctl error\n");
events = (struct epoll_event*)calloc(MAX_EVENTS, sizeof(events));
// start the event loop
printf("[*] Server started on localhost:%s\n", port);
while (1) {
// iterate through event
polled = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (i = 0; i < polled; i++) {
// check for errors
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN)))
{
CFree((Client*)events[i].data.ptr);
continue;
}
// handle incoming connections
else if (events[i].data.fd == server.fd) {
struct sockaddr addr;
socklen_t len = sizeof(addr);
// accept all connections
while (1) {
Client *sock = (Client*)malloc(sizeof(sock));
sock->req.path = NULL;
sock->fd = accept(server.fd, &addr, &len);
if (sock->fd < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
break;
free(sock);
CError("Socket accept failed!\n");
}
// set client socket to non-blocking
if (CBlocking(sock, 0) < 0) {
CFree(sock);
continue;
}
// register client socket to epoll
event.data.fd = sock->fd;
event.data.ptr = (void*)sock;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock->fd, &event) < 0) {
CFree(sock);
continue;
}
}
}
// handle reading data from client
else {
Client *sock = (Client*)events[i].data.ptr;
// start reading data from client
while (1) {
memset(buffer, 0, sizeof(buffer));
nread = read(sock->fd, buffer, sizeof(buffer));
// handle EOF
if (nread == -1) {
if (errno != EAGAIN)
CFree(sock);
break;
// handle empty data
} else if (nread == 0) {
break;
// handle data
} else {
HttpParse(buffer, &(sock->req));
if (sock->req.complete == 1)
Respond(sock);
}
}
}
}
}
// free all resources before exiting
free(events);
close(server.fd);
return 0;
}
all:
gcc -g -Wall -fPIC http.c main.c -o app
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment