Last active
December 19, 2015 17:39
-
-
Save minitech/5992776 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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