Created
September 10, 2013 14:27
-
-
Save minitech/6510183 to your computer and use it in GitHub Desktop.
The beginnings of an RSS reader or something in C.
This file contains 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 <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