Skip to content

Instantly share code, notes, and snippets.

@minitech
Created September 10, 2013 14:27
Show Gist options
  • Save minitech/6510183 to your computer and use it in GitHub Desktop.
Save minitech/6510183 to your computer and use it in GitHub Desktop.
The beginnings of an RSS reader or something in C.
#include <string.h>
#include <stdio.h>
#include <libpq-fe.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 <errno.h>
#include <expat.h>
#include "uthash.h"
int serve();
int main() {
const char* keywords[] = {"dbname", NULL};
const char* values[] = {"tailold", NULL};
PGconn* db = PQconnectdbParams(keywords, values, 0);
if(PQstatus(db) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(db));
PQfinish(db);
return 1;
}
PGresult* result = PQexec(db, "SELECT url FROM feeds");
if(PQresultStatus(result) != PGRES_TUPLES_OK) {
fprintf(stderr, "Failed to query feeds: %s", PQerrorMessage(db));
PQclear(result);
PQfinish(db);
return 1;
}
int count = PQntuples(result);
for(int i = 0; i < count; i++) {
puts(PQgetvalue(result, i, 0));
}
PQclear(result);
PQfinish(db);
//return serve();
return 0;
}
#define FREE_LINKED_LIST(type, head) { type l = head; while(l) { type next = l->next; free(l); l = next; } }
struct header {
char name[64];
struct header* next;
};
struct body_part {
struct body_part* next;
};
struct client {
int fd;
enum {
ERROR,
METHOD, PATH, PROTOCOL, PRE_HEADER_CR,
HEADER_NAME, HEADER_WHITESPACE, HEADER_VALUE, PRE_BODY_CR,
BODY,
DONE
} parse_state;
char method[16];
size_t method_index;
char path[8192];
size_t path_index;
size_t protocol_index;
char current_header_name[64];
size_t current_header_index;
struct header* request_headers;
struct body_part* request_body;
struct header* response_headers;
struct body_part* response_body;
size_t body_length;
size_t expected_body_length;
UT_hash_handle hh;
};
void free_client(struct client* c) {
/*FREE_LINKED_LIST(struct header*, c->request_headers)
FREE_LINKED_LIST(struct body_part*, c->request_body)
FREE_LINKED_LIST(struct header*, c->response_headers)
FREE_LINKED_LIST(struct body_part*, c->response_body)*/
free(c);
}
int continue_parse(struct client* client) {
char buffer[8192];
ssize_t r = read(client->fd, buffer, sizeof buffer);
if(r == -1) {
perror("Failed to read from client");
return 0;
}
printf("Read %ld bytes.\n", r);
for(ssize_t i = 0; i < r; i++) {
char c = buffer[i];
switch(client->parse_state) {
case METHOD:
if(c == ' ') {
client->parse_state = PATH;
client->method[client->method_index] = '\0';
printf("Got method %s!\n", client->method);
} else {
if(client->method_index >= sizeof client->method - 1) {
client->parse_state = ERROR;
char response[] = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/plain\r\n\r\nThe specified method was not recognized.";
write(client->fd, response, sizeof response - 1);
return 0;
}
client->method[client->method_index++] = c;
}
break;
case PATH:
if(c == ' ') {
client->parse_state = PROTOCOL;
client->path[client->path_index] = '\0';
printf("Got path %s!\n", client->path);
} else {
if(client->path_index >= sizeof client->path - 1) {
client->parse_state = ERROR;
char response[] = "HTTP/1.1 414 Request-URI Too Long\r\nContent-Type: text/plain\r\n\r\nThat request URI is too long.";
write(client->fd, response, sizeof response - 1);
return 0;
}
client->path[client->path_index++] = c;
}
break;
case PROTOCOL:
if(c == '\r') {
client->parse_state = PRE_HEADER_CR;
client->current_header_index = 0;
} else if(c == '\n') {
client->parse_state = HEADER_NAME;
client->current_header_index = 0;
} else {
#define EXPECTED_PREFIX "HTTP/1."
if(client->protocol_index >= sizeof EXPECTED_PREFIX) {
client->parse_state = ERROR;
char response[] = "HTTP/1.1 505 HTTP Version Not Supported\r\nContent-Type: text/plain\r\n\r\nThis server pretty much just supports HTTP/1.1.";
write(client->fd, response, sizeof response - 1);
return 0;
}
if(client->protocol_index == sizeof EXPECTED_PREFIX - 1) {
if(c != '1' && c != '0') {
client->parse_state = ERROR;
char response[] = "HTTP/1.1 505 HTTP Version Not Supported\r\nContent-Type: text/plain\r\n\r\nThis server pretty much just supports HTTP/1.1.";
write(client->fd, response, sizeof response - 1);
return 0;
}
printf("HTTP version is 1.%c\n", c);
} else if(c != EXPECTED_PREFIX[client->protocol_index]) {
client->parse_state = ERROR;
char response[] = "HTTP/1.1 505 HTTP Version Not Supported\r\nContent-Type: text/plain\r\n\r\nThis server pretty much just supports HTTP/1.1.";
write(client->fd, response, sizeof response - 1);
return 0;
}
client->protocol_index++;
}
break;
case PRE_HEADER_CR:
client->parse_state = HEADER_NAME;
if(c == '\n') {
break;
}
case HEADER_NAME:
if(c == '\r') {
// TODO: Ensure that the header name is empty in both cases
client->parse_state = PRE_BODY_CR;
} else if(c == '\n') {
client->parse_state = BODY;
} else if(c == ':') {
client->parse_state = HEADER_WHITESPACE;
} else if(client->current_header_index >= sizeof client->current_header_name - 1) {
client->parse_state = ERROR;
}
break;
case HEADER_WHITESPACE:
if(c == ' ') {
break;
}
client->parse_state = HEADER_VALUE;
case HEADER_VALUE:
if(c == '\r') {
client->parse_state = PRE_HEADER_CR;
} else if(c == '\n') {
client->parse_state = HEADER_NAME;
} else {
}
break;
case PRE_BODY_CR:
client->parse_state = BODY;
if(c == '\n') {
break;
}
case BODY:
client->body_length++;
break;
default:
fputs("Parsing error.\n", stderr);
return 0;
}
}
if(client->body_length >= client->expected_body_length) {
if(client->body_length > client->expected_body_length) {
fputs("Got more body than expected; ignoring.\n", stderr);
}
client->parse_state = DONE;
char response_body[] = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Some good news</title></head><body><h1>Congratulations!</h1><p>It worked!</p></body></html>";
char response[8192];
snprintf(response, sizeof response, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s", sizeof response_body - 1, response_body);
write(client->fd, response, strlen(response));
return 0;
}
return 1;
}
int serve() {
int port = 5000;
struct in_addr address;
inet_pton(AF_INET, "127.0.0.1", &address);
int s = socket(AF_INET, 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_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_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;
}
struct epoll_event events[1024];
struct client* clients = NULL;
while(1) {
int n = epoll_wait(epoll, events, sizeof(events) / sizeof(struct epoll_event), -1);
if(n == -1) {
if(errno == EINTR) {
// So this just happens, huh?
// http://stackoverflow.com/questions/2252981/gdb-error-unable-to-execute-epoll-wait-4-interrupted-system-call
continue;
}
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_in 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");
continue;
}
char readable_address[INET6_ADDRSTRLEN + 1];
inet_ntop(AF_INET, &cli_addr.sin_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 | EPOLLRDHUP;
ev.data.fd = client;
if(epoll_ctl(epoll, EPOLL_CTL_ADD, client, &ev) == -1) {
perror("Adding client socket failed");
close(client);
} else {
struct client* c = malloc(sizeof(struct client));
c->fd = client;
c->method_index = 0;
c->path_index = 0;
c->protocol_index = 0;
c->body_length = 0;
c->expected_body_length = 0;
c->parse_state = METHOD;
struct client* e;
HASH_FIND_INT(clients, &client, e);
if(e) {
HASH_DEL(clients, e);
free(e);
}
HASH_ADD_INT(clients, fd, c);
}
} else {
// Receiving from a client
int e = events[i].events;
int fd = events[i].data.fd;
struct client* c;
HASH_FIND_INT(clients, &fd, c);
if(!c) {
fprintf(stderr, "Failed to retrieve client %d from hash. (Bad)\n", fd);
close(s);
close(epoll);
return 1;
}
if(e & EPOLLRDHUP) {
puts("EPOLLRDHUP");
close(fd);
//epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, &ev);
// ^ Somebody said this was automatic
HASH_DEL(clients, c);
free_client(c);
} else if(e & EPOLLERR) {
perror("EPOLLERR result");
close(fd);
//epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, &ev);
HASH_DEL(clients, c);
free_client(c);
} else if(e & EPOLLIN) {
if(!continue_parse(c)) {
close(fd);
HASH_DEL(clients, c);
free_client(c);
}
}
}
}
}
close(s);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment