├── .gitignore ├── Makefile ├── README.md ├── bogart.c ├── bogart.h ├── example.c ├── index.cml ├── trie.c └── trie.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | /example -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=clang 2 | 3 | all: bogart.o example.o trie.o example 4 | 5 | bogart.o: bogart.c bogart.h 6 | $(CC) -ggdb -c bogart.c -o bogart.o -I/Users/tyler/Code/hiredis 7 | 8 | trie.o: trie.c trie.h 9 | $(CC) -ggdb -c trie.c -o trie.o 10 | 11 | example.o: example.c 12 | $(CC) -ggdb -c example.c -o example.o -I/Users/tyler/Code/hiredis 13 | 14 | example: example.o bogart.o 15 | $(CC) -ggdb -o example bogart.o trie.o example.o -levent -L/Users/tyler/Code/hiredis -lhiredis 16 | 17 | clean: 18 | rm *.o example 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bogart 2 | ------ 3 | 4 | Bogart is a (mostly facetious) response to the Sinatra project. The primary difference 5 | between the two projects is that Bogart is written in C, with the purpose of making it quick 6 | and easy to write little web services in C. 7 | 8 | This code has every security flaw you imagine, tons of memory leaks, and when I wrote it I 9 | may have been awake for much longer than one should be when writing C. 10 | 11 | A secondary reason for its existence was to give me an excuse to play with Apple's anonymous 12 | function extension to C. So, given that, it will only compile on OS X. 13 | 14 | It uses libevent, so you'll need that. 15 | 16 | If I get around to finishing it, it'll also support simple little models using Redis. 17 | 18 | 19 | Example 20 | ------- 21 | 22 | #include 23 | #include "bogart.h" 24 | 25 | Bogart { 26 | UseRedis; 27 | 28 | get("/hello") { 29 | body("

Hello, World!

"); 30 | }; 31 | 32 | get("/create") { 33 | redisCommand(_redisFd, "HSET User:%s %s %s", params("id"), "name", params("name")); 34 | body("User created."); 35 | }; 36 | 37 | get("/show") { 38 | redisReply * reply = redisCommand(_redisFd, "HGET User:%s %s", params("id"), "name"); 39 | view("index.cml", map("name", reply->reply)); 40 | }; 41 | 42 | Start(11000) 43 | } 44 | 45 | 46 | Pretty stupid simple. 47 | 48 | You might notice that "index.cml" bit in the last route. I rolled a crappy little template 49 | system into it. You pass `view` a filename and a mapping of keys to strings. 50 | 51 | 52 | So, check it out if you're curious how it's done... But don't use it for anything. Ever. 53 | 54 | 55 | [Tyler McMullen](http://drmcawesome.com/) 56 | -------------------------------------------------------------------------------- /bogart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "bogart.h" 7 | 8 | // hax! libevent doesn't export evkeyvalq definition properly 9 | #include 10 | TAILQ_HEAD (evkeyvalq, evkeyval); 11 | 12 | #define BOGART_NOT_FOUND_DEFAULT ^ void (Request * request, Response * response) {} 13 | 14 | 15 | Map makeMap(void * dummy, ...) { 16 | va_list ap; 17 | char * key, * value; 18 | Map newMap = Trie_new(); 19 | 20 | va_start(ap, dummy); 21 | while((key = va_arg(ap, char *)) && (value = va_arg(ap, char *))) { 22 | Trie_add(newMap, key, strlen(key), value); 23 | } 24 | 25 | return newMap; 26 | } 27 | 28 | Response * Response_new(struct evbuffer * buffer) { 29 | Response * r = (Response *) malloc(sizeof(Response)); 30 | r->code = 200; 31 | r->buffer = buffer; 32 | return r; 33 | } 34 | 35 | void Response_free(Response * r) { 36 | free(r); 37 | } 38 | 39 | Request * Request_new(struct evhttp_request * ev_req) { 40 | Request * r = (Request *) malloc(sizeof(Request)); 41 | r->ev_req = ev_req; 42 | r->uri = evhttp_request_uri(ev_req); 43 | r->params = (struct evkeyvalq *) malloc(sizeof(struct evkeyvalq)); 44 | evhttp_parse_query(r->uri, r->params); 45 | return r; 46 | } 47 | 48 | void Request_free(Request * r) { 49 | free(r); 50 | } 51 | 52 | 53 | bool match_uri(const char * pattern, const char * uri) { 54 | while(*pattern && *uri) { 55 | if(*pattern == *uri) { 56 | pattern++; 57 | uri++; 58 | } else if(*pattern == '*') { 59 | if(*uri == '/' || !*(uri+1)) { 60 | pattern++; 61 | } else { 62 | uri++; 63 | } 64 | } else { 65 | return false; 66 | } 67 | } 68 | return (!*pattern && !*uri) || (!*pattern && *uri); 69 | } 70 | 71 | Route * match_route(Route * route, Request * req) { 72 | while(route) { 73 | if(req->ev_req->type == route->type && match_uri(route->pattern, req->ev_req->uri)) 74 | return route; 75 | route = route->next; 76 | } 77 | 78 | return NULL; 79 | } 80 | 81 | Route * nextRoute(char * pattern, enum evhttp_cmd_type type, BogartContext * bogart) { 82 | Route * new_route = (Route *) malloc(sizeof(Route)); 83 | new_route->pattern = pattern; 84 | if(bogart->route) { 85 | Route * cursor = bogart->route; 86 | while(cursor->next) 87 | cursor = cursor->next; 88 | cursor->next = new_route; 89 | } else { 90 | bogart->route = new_route; 91 | } 92 | return new_route; 93 | } 94 | 95 | const char * getParam(Request * request, const char * key) { 96 | return evhttp_find_header(request->params, key); 97 | } 98 | 99 | void renderText(Response * response, char * template, Map args) { 100 | char anchor[] = "%{"; 101 | char * cursor; 102 | char * val; 103 | while((cursor = strstr(template, anchor))) { 104 | evbuffer_add(response->buffer, template, cursor - template); 105 | template = cursor + sizeof(anchor) - 1; 106 | cursor = strchr(template, '}'); 107 | val = Trie_get(args, template, cursor - template); 108 | evbuffer_add(response->buffer, val, strlen(val)); 109 | template = cursor + 1; 110 | } 111 | evbuffer_add(response->buffer, template, strlen(template)); 112 | } 113 | 114 | char * loadTemplate(char * filename) { 115 | char * result; 116 | int size = 0; 117 | FILE *f = fopen(filename, "r"); 118 | if(!f) return NULL; 119 | 120 | fseek(f, 0, SEEK_END); 121 | size = ftell(f); 122 | fseek(f, 0, SEEK_SET); 123 | 124 | result = (char *) malloc(size + 1); 125 | fread(result, sizeof(char), size, f); 126 | fclose(f); 127 | 128 | result[size] = 0; 129 | 130 | return result; 131 | } 132 | 133 | void renderTemplate(Response * response, char * filename, Map args) { 134 | char * template = loadTemplate(filename); 135 | renderText(response, template, args); 136 | free(template); 137 | } 138 | 139 | void setBody(Response * response, const char * pattern, ...) { 140 | va_list ap; 141 | va_start(ap, pattern); 142 | evbuffer_add_vprintf(response->buffer, pattern, ap); 143 | } 144 | 145 | void finalizeRoutes(Route * route) { 146 | while(route) { 147 | route->handler = Block_copy(route->handler); 148 | route = route->next; 149 | } 150 | } 151 | 152 | void setupHandlers(BogartContext *); 153 | 154 | void request_handler(struct evhttp_request * ev_req, void * context) { 155 | struct timeval t0, t1, tr; 156 | 157 | BogartContext * bogart = (BogartContext *) context; 158 | 159 | gettimeofday(&t0, NULL); 160 | 161 | Request * request = Request_new(ev_req); 162 | Response * response = Response_new(evbuffer_new()); 163 | 164 | Route * matched_route = match_route(bogart->route, request); 165 | 166 | if(matched_route) { 167 | matched_route->handler(request, response); 168 | evhttp_send_reply(ev_req, response->code, "OK", response->buffer); 169 | } else { 170 | bogart->not_found(request, response); 171 | evhttp_send_reply(ev_req, 404, "Not Found", response->buffer); 172 | } 173 | 174 | evbuffer_free(response->buffer); 175 | 176 | gettimeofday(&t1, NULL); 177 | timersub(&t1, &t0, &tr); 178 | printf("Request processed in: %ld secs, %d usecs\n", tr.tv_sec, tr.tv_usec); 179 | } 180 | 181 | void setupBogart(BogartContext * bogart) { 182 | bogart->not_found = BOGART_NOT_FOUND_DEFAULT; 183 | setupHandlers(bogart); 184 | } 185 | 186 | void startBogart(uint16_t port, BogartContext * bogart) { 187 | bogart->port = port; 188 | 189 | setupBogart(bogart); 190 | 191 | struct event_base * base = event_init(); 192 | struct evhttp * http = evhttp_new(base); 193 | evhttp_bind_socket(http, "0.0.0.0", bogart->port); 194 | evhttp_set_gencb(http, request_handler, bogart); 195 | 196 | printf("Showtime! Bogart's ready on camera %u...\n", bogart->port); 197 | 198 | event_loop(0); 199 | 200 | evhttp_free(http); 201 | } 202 | /* 203 | void modelCreate(RedisModel model, char * fields[], char * key, Map attrs) { 204 | int i; 205 | for(i = 0; i < model.fieldCount ; i++) { 206 | char * field = fields[i]; 207 | char * attr = Trie_get(attrs, field, strlen(field)); 208 | if(attr) 209 | redisCommand(model.redisFd, "HSET %s:%s %s %s", model.name, key, field, attr); 210 | } 211 | } 212 | 213 | Map modelGet(RedisModel model, char * fields[], char * key) { 214 | int i; 215 | Map record = Trie_new(); 216 | for(i = 0; i < model.fieldCount ; i++) { 217 | char * field = fields[i]; 218 | redisReply * reply = redisCommand(model.redisFd, "HGET %s:%s %s", model.name, key, field); 219 | if(reply->type == REDIS_REPLY_STRING) 220 | Trie_add(record, field, strlen(field), reply->reply); 221 | } 222 | } 223 | */ 224 | -------------------------------------------------------------------------------- /bogart.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "trie.h" 8 | 9 | typedef struct { 10 | const char * uri; 11 | struct evkeyvalq * params; 12 | struct evhttp_request * ev_req; 13 | } Request; 14 | 15 | typedef struct { 16 | int code; 17 | struct evbuffer * buffer; 18 | } Response; 19 | 20 | typedef void (^Handler)(Request *, Response *); 21 | 22 | typedef struct _Route { 23 | enum evhttp_cmd_type type; 24 | char * pattern; 25 | Handler handler; 26 | struct _Route * next; 27 | } Route; 28 | 29 | typedef struct { 30 | uint16_t port; 31 | void (^ init_func)(); 32 | Handler not_found; 33 | Route * route; 34 | } BogartContext; 35 | 36 | #define Bogart \ 37 | BogartContext globalContext; \ 38 | void setupHandlers(BogartContext * bogart) 39 | 40 | #define Start(_port) \ 41 | finalizeRoutes(bogart->route); \ 42 | } \ 43 | int main() { \ 44 | startBogart(_port, &globalContext); \ 45 | return 0; 46 | 47 | #define get(_pattern) \ 48 | nextRoute(_pattern, EVHTTP_REQ_GET, bogart)->handler = ^ void (Request * request, Response * response) 49 | 50 | #define post(_pattern) \ 51 | nextRoute(_pattern, EVHTTP_REQ_POST, bogart)->handler = ^ void (Request * request, Response * response) 52 | 53 | #define status(_status) \ 54 | response->code = _status; 55 | 56 | #define body(_pattern, ...) \ 57 | setBody(response, _pattern, ##__VA_ARGS__) 58 | 59 | #define params(_key) getParam(request, _key) 60 | 61 | const char * getParam(Request *, const char *); 62 | void setBody(Response *, const char *, ...); 63 | void startBogart(uint16_t, BogartContext *); 64 | Route * nextRoute(char *, enum evhttp_cmd_type, BogartContext *); 65 | void finalizeRoutes(Route *); 66 | 67 | typedef struct { 68 | char * key; 69 | char * value; 70 | } CharTuple; 71 | 72 | typedef Trie * Map; 73 | 74 | #define map(...) makeMap(NULL, __VA_ARGS__, NULL) 75 | Map makeMap(void *, ...); 76 | 77 | #define render(_template, _map) renderText(response, _template, _map) 78 | void renderText(Response *, char *, Map); 79 | 80 | #define view(_filename, _map) renderTemplate(response, _filename, _map) 81 | void renderTemplate(Response *, char *, Map); 82 | 83 | /* 84 | typedef struct _ModelField { 85 | char * fieldName; 86 | struct _ModelField * next; 87 | } ModelField; 88 | 89 | typedef struct { 90 | char * name; 91 | int redisFd; 92 | ModelField * field; 93 | } RedisModel; 94 | 95 | void modelCreate(RedisModel, char **, char *, Map); 96 | Map modelGet(RedisModel, char **, char *); 97 | 98 | #define Model(_name, ...) \ 99 | char * _name##_fields[] = __VA_ARGS__; \ 100 | RedisModel _name##Model = { #_name, _redisFd, _name##_fields, sizeof(_name##_fields) } 101 | */ 102 | 103 | #define UseRedis \ 104 | int _redisFd; \ 105 | redisConnect(&_redisFd, "127.0.0.1", 6379) 106 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bogart.h" 3 | 4 | Bogart { 5 | UseRedis; 6 | //Model(User, "name", "email"); 7 | 8 | get("/hello") { 9 | body("

Hello, World!

"); 10 | }; 11 | 12 | get("/create") { 13 | redisCommand(_redisFd, "HSET User:%s %s %s", params("id"), "name", params("name")); 14 | body("User created."); 15 | }; 16 | 17 | get("/show") { 18 | redisReply * reply = redisCommand(_redisFd, "HGET User:%s %s", params("id"), "name"); 19 | view("index.cml", map("name", reply->reply)); 20 | }; 21 | 22 | Start(11000) 23 | } 24 | -------------------------------------------------------------------------------- /index.cml: -------------------------------------------------------------------------------- 1 | 2 | 3 | OMG 4 | 5 | 6 |

%{name} is in the house!

7 | 8 | 9 | -------------------------------------------------------------------------------- /trie.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct _Trie { 6 | char state; 7 | char * value; 8 | struct _Trie * sibling; 9 | struct _Trie * children; 10 | }; 11 | typedef struct _Trie Trie; 12 | 13 | Trie * Trie_new() { 14 | Trie * trie = (Trie *) malloc(sizeof(Trie)); 15 | trie->state = '\0'; 16 | trie->value = NULL; 17 | trie->sibling = NULL; 18 | trie->children = NULL; 19 | return trie; 20 | } 21 | 22 | void Trie_free(Trie * trie) { 23 | if(trie->sibling) 24 | Trie_free(trie->sibling); 25 | if(trie->children) 26 | Trie_free(trie->children); 27 | free(trie); 28 | } 29 | 30 | Trie * Trie_node(char state) { 31 | Trie * trie = Trie_new(); 32 | trie->state = state; 33 | return trie; 34 | } 35 | 36 | void Trie_add(Trie * trie, char * data, int length, char * value) { 37 | // look for the child 38 | Trie * child = trie->children; 39 | while(child) { 40 | if(child->state == *data) 41 | break; 42 | child = child->sibling; 43 | } 44 | 45 | // if the child doesn't exist add it 46 | if(!child) { 47 | child = Trie_node(*data); 48 | child->sibling = trie->children; 49 | trie->children = child; 50 | } 51 | 52 | if(length == 1) { 53 | child->value = value; 54 | } else { 55 | // oh, tail recursion... how I wish you existed in C. 56 | Trie_add(child, data + 1, length - 1, value); 57 | } 58 | } 59 | 60 | char * Trie_get(Trie * trie, char * data, int length) { 61 | Trie * child = trie; 62 | 63 | // look for the child 64 | while(length > 0) { 65 | child = child->children; 66 | while(child) { 67 | if(child->state == *data) 68 | break; 69 | child = child->sibling; 70 | } 71 | if(!child) 72 | return NULL; 73 | 74 | length--; 75 | data++; 76 | } 77 | 78 | return child->value; 79 | } 80 | 81 | /* 82 | int main() { 83 | Trie * trie = Trie_new(); 84 | Trie_add(trie, "foo", 3, "foo"); 85 | Trie_add(trie, "bar", 3, "bar"); 86 | Trie_add(trie, "food", 4, "food"); 87 | 88 | printf("foo: %s\n", Trie_get(trie, "foo", 3)); 89 | printf("food: %s\n", Trie_get(trie, "food", 4)); 90 | printf("bar: %s\n", Trie_get(trie, "bar", 3)); 91 | printf("argh: %s\n", Trie_get(trie, "argh", 4)); 92 | } 93 | */ 94 | -------------------------------------------------------------------------------- /trie.h: -------------------------------------------------------------------------------- 1 | struct _Trie { 2 | char state; 3 | char * value; 4 | struct _Trie * sibling; 5 | struct _Trie * children; 6 | }; 7 | typedef struct _Trie Trie; 8 | 9 | Trie * Trie_new(); 10 | void Trie_free(Trie *); 11 | Trie * Trie_node(char); 12 | void Trie_add(Trie *, char *, int, char *); 13 | char * Trie_get(Trie *, char *, int); 14 | --------------------------------------------------------------------------------