Skip to content

Instantly share code, notes, and snippets.

@minitech
Last active December 19, 2015 17:39
Show Gist options
  • Save minitech/5992776 to your computer and use it in GitHub Desktop.
Save minitech/5992776 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include "build/stylesheets.h"
#include "build/templates.h"
#include "build/pages.h"
int serve_client(int client);
int main() {
int port = 5000;
struct in6_addr address;
inet_pton(AF_INET6, "::1", &address);
int s = socket(AF_INET6, SOCK_STREAM, 0);
if(s < 0) {
perror("Error opening socket");
return 1;
}
int optval = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
struct sockaddr_in6 serv_addr;
serv_addr.sin6_family = AF_INET6;
serv_addr.sin6_port = htons(port);
serv_addr.sin6_addr = address;
if(bind(s, (struct sockaddr*) &serv_addr, sizeof serv_addr) < 0) {
perror("Could not bind to port");
close(s);
return 1;
}
listen(s, SOMAXCONN);
int epoll = epoll_create1(0);
if(epoll == -1) {
perror("epoll_create1 failed");
close(s);
return 1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = s;
if(epoll_ctl(epoll, EPOLL_CTL_ADD, s, &ev) == -1) {
perror("Adding server socket failed");
close(s);
close(epoll);
return 1;
}
printf("Listening with a backlog size of %d.\n", SOMAXCONN);
struct epoll_event events[1024];
while(1) {
int n = epoll_wait(epoll, events, sizeof(events) / sizeof(struct epoll_event), -1);
if(n == -1) {
perror("epoll_wait failed");
close(s);
close(epoll);
return 1;
}
for(int i = 0; i < n; i++) {
if(events[i].data.fd == s) {
// Accepting a client
struct sockaddr_in6 cli_addr;
socklen_t clilen = sizeof(struct sockaddr_in);
int client = accept(s, (struct sockaddr*) &cli_addr, &clilen);
if(client < 0) {
perror("Error accepting client");
close(s);
return 1;
}
char readable_address[46];
inet_ntop(AF_INET6, &cli_addr.sin6_addr, readable_address, sizeof readable_address);
printf("Got client from %s\n", readable_address);
int flags = fcntl(client, F_GETFL, 0);
fcntl(client, F_SETFL, flags == -1 ? O_NONBLOCK : flags | O_NONBLOCK);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if(epoll_ctl(epoll, EPOLL_CTL_ADD, client, &ev) == -1) {
perror("Adding client socket failed");
close(client);
close(s);
close(epoll);
return 1;
}
} else {
// Receiving from a client
serve_client(events[i].data.fd);
}
}
}
close(s);
return 0;
}
struct limited_string {
char* value;
int index;
int capacity;
};
struct limited_string create_limited_string(int capacity) {
struct limited_string str;
str.value = malloc(capacity + 1);
str.capacity = capacity;
str.index = 0;
return str;
}
int append_limited_string(struct limited_string* str, char c) {
if(str->index == str->capacity) {
return 0;
}
str->value[str->index++] = c;
return 1;
}
#define NEXT(state) { input_state = state; break; }
#define PASS(state) { input_state = state; i--; break; }
int serve_client(int client) {
char buffer[1024];
enum {
METHOD, PATH, PROTOCOL, PROTOCOL_CR,
HEADER_NAME, HEADER_WHITESPACE, HEADER_VALUE, HEADER_VALUE_CR
} input_state = METHOD;
struct limited_string method = create_limited_string(8);
struct limited_string path = create_limited_string(256);
while(1) {
int n = read(client, buffer, sizeof buffer);
if(!n) {
puts("Done here!");
return 0;
}
for(int i = 0; i < n; i++) {
char c = buffer[i];
switch(input_state) {
case METHOD:
if(c == ' ') {
if(strcmp("GET", method.value)) {
char response[] = "HTTP/1.1 405 Method Not Supported\r\n";
write(client, response, sizeof response - 1);
return 1;
}
NEXT(PATH)
}
append_limited_string(&method, c);
break;
case PATH:
if(c == ' ') {
strtok(path.value, "?");
if(!strcmp("/stylesheets/default.css", path.value)) {
char headers[1024] = "HTTP/1.1 200 OK\r\nContent-Type: text/css;charset=utf-8\r\n";
const char body[] = CSS_DEFAULT;
sprintf(headers + strlen(headers), "Content-Length: %lu\r\n\r\n", sizeof body - 1);
write(client, headers, strlen(headers));
write(client, body, sizeof body - 1);
} else if(!strcmp("/", path.value)) {
char headers[1024] = "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=utf-8\r\n";
const char body[] = TEMPLATE_HOME;
sprintf(headers + strlen(headers), "Content-Length: %lu\r\n\r\n", sizeof body - 1);
write(client, headers, strlen(headers));
write(client, body, sizeof body - 1);
} else {
char headers[1024] = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html;charset=utf-8\r\nConnection: close\r\n";
const char body[] = TEMPLATE_404;
sprintf(headers + strlen(headers), "Content-Length: %lu\r\n\r\n", sizeof body - 1);
write(client, headers, strlen(headers));
write(client, body, sizeof body - 1);
}
NEXT(PROTOCOL)
}
append_limited_string(&path, c);
break;
case PROTOCOL:
if(c == '\r') {
NEXT(PROTOCOL_CR)
}
if(c == '\n') {
NEXT(HEADER_NAME)
}
break;
case PROTOCOL_CR:
if(c == '\n') {
NEXT(HEADER_NAME)
}
PASS(HEADER_NAME)
case HEADER_NAME:
if(c == '\n') {
printf("Finished %s request for %s.\n", method.value, path.value);
return 1;
}
if(c == ':') {
NEXT(HEADER_WHITESPACE)
}
putchar(c);
break;
case HEADER_WHITESPACE:
if(c != ' ') {
putchar('=');
PASS(HEADER_VALUE)
}
break;
case HEADER_VALUE:
if(c == '\r') {
putchar('\n');
NEXT(HEADER_VALUE_CR)
}
if(c == '\n') {
putchar('\n');
NEXT(HEADER_NAME)
}
putchar(c);
break;
case HEADER_VALUE_CR:
if(c == '\n') {
NEXT(HEADER_NAME)
}
PASS(HEADER_NAME)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment