/* gcc -I/usr/local/include -levent evhttpd.c -o evhttpd */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "evapp.h" static void response_not_found(struct evhttp_request *request) { /* If unhandled, let them know */ struct evbuffer *databuf = evbuffer_new(); char *escaped = evhttp_htmlescape(request->uri); assert(databuf != NULL); evbuffer_add_printf(databuf, "Page Not Found" "O HAI
CULD NOT FIND PAJ: %s", escaped); free(escaped); evhttp_send_reply(request, HTTP_NOTFOUND, "NOT FOUND", databuf); return; } static void request_router(struct evhttp_request *request, void *arg) { struct evapp_context *ctx = (struct evapp_context *)arg; struct evapp_urimap *map = ctx->maps.tqh_first; /* Forward traverse the list of mappings. This means * that a map of "/" will beat everything if it is * listed first. */ for (; map != NULL; map = map->entries.tqe_next) { if (map->type == EVAPP_URIMAP_EXACT) { if (!strcmp(map->uri, evhttp_request_uri(request))) { ctx->request = request; evbuffer_drain(ctx->response, EVBUFFER_LENGTH(ctx->response)); map->handler(ctx); return; } } else { if (!strncmp(map->uri, evhttp_request_uri(request), strlen(map->uri))) { ctx->request = request; evbuffer_drain(ctx->response, EVBUFFER_LENGTH(ctx->response)); map->handler(ctx); return; } } } /* TODO: add a simple override. _prefix.ct in / would work too */ response_not_found(request); return; } int evapp_register_handler(struct evapp_context *ctx, short type, const char *const uri, void (*handler)(struct evapp_context *)) { struct evapp_urimap *map = (struct evapp_urimap *)malloc(sizeof(struct evapp_urimap)); assert(uri != NULL); assert(handler != NULL); assert(type == 1 || type == 2); if (map == NULL) { // logging return -1; } map->uri = (char *)calloc(1, strlen(uri)+1); if (map->uri == NULL) { // logging free(map); return -1; } strcpy(map->uri, uri); map->handler = handler; map->type = type; TAILQ_INSERT_TAIL(&ctx->maps, map, entries); fprintf(stderr, "[register_handler] %d:%s registered\n", type, uri); return 0; } bool evapp_dbenv_init(struct evapp_context *ctx, const char *const path) { int rc = 0; ctx->dbenv = NULL; if ((rc = db_env_create(&ctx->dbenv, 0)) != 0) { fprintf(stderr, "[db_init] failed to create db env: %s\n", db_strerror(rc)); return false; } /* set in-memory log buffer size */ /* TODO: Make this all configurable - getopt or at least defines! */ if ((rc = ctx->dbenv->set_lg_bsize(ctx->dbenv, 10 * 1024 * 1024)) != 0) { fprintf(stderr, "[db_init] failed to increase log size: %s\n", db_strerror(rc)); return false; } if ((rc = ctx->dbenv->set_cachesize(ctx->dbenv, 0, 10 * 1024 * 1024, 1)) != 0) { fprintf(stderr, "[db_init] failed to increase cache size: %s\n", db_strerror(rc)); return false; } if ((rc = ctx->dbenv->open(ctx->dbenv, path, DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG| DB_INIT_MPOOL|DB_INIT_TXN, 0)) != 0) { fprintf(stderr, "[db_init] failed to open db env: %s\n", db_strerror(rc)); return false; } /* Don't block on writes until it is optimal * TODO: determine if this works like it seems like it will ;-) */ if ((rc = ctx->dbenv->set_flags(ctx->dbenv, DB_TXN_WRITE_NOSYNC|DB_AUTO_COMMIT, 1)) != 0) { fprintf(stderr, "[db_init] failed to set DB_TXN_WRITE_NOSYNC: %s\n", db_strerror(rc)); ctx->dbenv->close(ctx->dbenv, 0); return false; } return true; } /* This function setups up secondary databases that allow DUPs */ bool evapp_db_add_index(evapp_ctx *ctx, struct evapp_db *db, const char *field, const char *type, const char *file, int (*callback)(DB*, const DBT*, const DBT*, DBT*)) { struct evapp_db_index *index = NULL; int rc = 0; if (!ctx || !db || !field || !callback) return false; /* Step one: add the index object */ index = malloc(sizeof(struct evapp_db_index)); if (!index) { // XXX: log! return false; } index->callback = callback; /* Now setup the secondary database */ if ((rc = db_create(&index->p, ctx->dbenv, 0)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_init] failed to create handle."); free(index); return false; } /* Set some default flags */ if ((rc = index->p->set_flags(index->p, DB_DUP|DB_DUPSORT)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_add] set DB flags"); /* XXX: We'll leak memory here, but it doesn't make sense to close the env. */ index->p->close(index->p, 0); free(index); return false; } /* Now open the db --> do we want DIRTY_READs? */ if ((rc = index->p->open(index->p, NULL, file, field, DB_BTREE, DB_CREATE|DB_DIRTY_READ|DB_AUTO_COMMIT, 0600)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_add_index] failed to open db."); /* XXX: We'll leak memory here, but it doesn't make sense to close the env. */ free(index); return false; } if ((rc = db->p->associate(db->p, NULL, index->p, callback, DB_CREATE)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_add_index] failed to associate dbs."); /* XXX: We'll leak memory here, but it doesn't make sense to close the env. */ index->p->close(index->p, 0); free(index); return false; } index->file = strdup(file); index->field = strdup(field); index->type = strdup(type); TAILQ_INSERT_TAIL(&db->indices, index, entries); return true; } struct evapp_db_index *evapp_db_index_select(evapp_ctx *ctx, struct evapp_db *db, const char *const name) { struct evapp_db_index *entry = NULL; for (entry = db->indices.tqh_first; entry != NULL; entry = entry->entries.tqe_next) if (!strcmp(entry->field, name)) return entry; return NULL; } struct evapp_db *evapp_db_select(struct evapp_context *ctx, const char *const name) { struct evapp_db *entry = NULL; for (entry = ctx->dbs.tqh_first; entry != NULL; entry = entry->entries.tqe_next) if (!strcmp(entry->name, name)) return entry; return NULL; } struct evapp_db_index *evapp_db_index_first(evapp_ctx *ctx, struct evapp_db *db) { if (!db) return NULL; return db->indices.tqh_first; } // XXX: add abstracted iterators instead struct evapp_dbq *evapp_db_head(struct evapp_context *ctx) { return &ctx->dbs; } bool evapp_db_set_validate(evapp_ctx *ctx, const char *dbname, bool (*validate)(evapp_ctx*, HDF*)) { struct evapp_db *db = NULL; if (!ctx || !dbname || !validate) return false; db = evapp_db_select(ctx, dbname); if (!db) return false; db->validate = validate; return true; } bool evapp_db_add(struct evapp_context *ctx, const char *const name, const char *const table, bool (*load)(evapp_ctx*, u_int64_t, HDF**), bool (*save)(evapp_ctx*, u_int64_t, HDF*), bool (*del)(evapp_ctx*, u_int64_t), bool (*hdf)(evapp_ctx*, void*, size_t, HDF**), bool (*hdf_template)(evapp_ctx*, HDF**), bool (*cleanup)(void), bool (*validate)(evapp_ctx*, HDF*)) { struct evapp_db *db; int rc = 0; assert(ctx != NULL); db = (struct evapp_db *) malloc(sizeof(struct evapp_db)); if (db == NULL) /* TODO: log */ return false; TAILQ_INIT(&db->indices); db->name = strdup(name); db->file = strdup(table); db->p = NULL; // XXX: should we enforce any? db->load = load; db->save = save; db->del = del; db->hdf = hdf; db->hdf_template = hdf_template; db->cleanup = cleanup; db->validate = validate; if ((rc = db_create(&db->p, ctx->dbenv, 0)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_init] failed to create handle."); free(db); return false; } /* Disallow DUPs on primary databases */ #if 0 if ((rc = db->p->set_flags(db->p, DB_DUP)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_add] set DB flags"); /* XXX: We'll leak memory here, but it doesn't make sense to close the env. */ db->p->close(db->p, 0); free(db); return false; } #endif /* Now open the db */ if ((rc = db->p->open(db->p, NULL, db->file, db->name, DB_BTREE, DB_CREATE|DB_DIRTY_READ|DB_AUTO_COMMIT, 0600)) != 0) { ctx->dbenv->err(ctx->dbenv, rc, "[db_add] failed to open db."); /* XXX: We'll leak memory here, but it doesn't make sense to close the env. */ free(db); return false; } /*** XXX: add DB_TXN_NOT_DURABLE and then add timers which flush to disk * using DB_ENV->txn_checkpoint or DB_ENV->memp_sync. * I wonder how that would work in a ulthreaded context? */ /* XXX: later add support for application supplied DUPSORT */ TAILQ_INSERT_TAIL(&ctx->dbs, db, entries); return true; } struct evapp_context *evapp_new(const char *const name, void *app_state) { struct evapp_context *ctx = (struct evapp_context *) malloc(sizeof(struct evapp_context)); if (ctx == NULL) /* TODO: log */ return NULL; TAILQ_INIT(&ctx->maps); TAILQ_INIT(&ctx->dbs); ctx->request = NULL; ctx->state = app_state; ctx->name = strdup(name); ctx->default_domain = NULL; ctx->debug_mode = false; ctx->default_templates = true; ctx->xsrf_protection = true; ctx->server = NULL; ctx->dbenv = NULL; ctx->response = evbuffer_new(); if (!ctx->response) { free(ctx); return NULL; } ctx->server = evhttp_new(ctx->event_base); if (ctx->server == NULL) { free(ctx->response); free(ctx); return false; } ctx->event_base = event_init(); evtag_init(); return ctx; } bool evapp_bind(struct evapp_context *ctx, const char *const bindaddr, uint16_t port) { char *name = ""; assert(ctx != NULL); if (ctx->name) name = ctx->name; openlog(name, LOG_PID|LOG_CONS, LOG_DAEMON); fprintf(stderr, "[main] %s %s is starting up . . .\n", name, EVAPP_VERSION); if (evhttp_bind_socket(ctx->server, bindaddr, port) == -1) { fprintf(stderr, "[main] failed to bind web server to port %d\n", port); return false; } fprintf(stderr, "[main] web server is running on port %d\n", port); evhttp_set_gencb(ctx->server, request_router, (void *)ctx); return true; } void evapp_serve() { event_dispatch(); return; } /* XXX: should there be a separate shutdown mechanism? */ void evapp_destroy(struct evapp_context *ctx) { struct evapp_urimap *map = NULL; struct evapp_db *db = NULL; struct evapp_db_index *index = NULL; while ((map = ctx->maps.tqh_first) != NULL) { TAILQ_REMOVE(&ctx->maps, ctx->maps.tqh_first, entries); if (map->uri) free(map->uri); free(map); } while ((db = ctx->dbs.tqh_first) != NULL) { TAILQ_REMOVE(&ctx->dbs, ctx->dbs.tqh_first, entries); if (db->cleanup) db->cleanup(); if (db->name) free(db->name); if (db->file) free(db->file); if (db->p) db->p->close(db->p, 0); while ((index = db->indices.tqh_first) != NULL) { TAILQ_REMOVE(&db->indices, db->indices.tqh_first, entries); if (index->field) free(index->field); if (index->file) free(index->file); if (index->type) free(index->type); if (index->p) index->p->close(index->p, 0); free(index); } free(db); } if (ctx->dbenv) ctx->dbenv->close(ctx->dbenv, 0); if (ctx->name) free(ctx->name); if (ctx->server) evhttp_free(ctx->server); if (ctx->event_base) event_base_free(ctx->event_base); if (ctx->response) evbuffer_free(ctx->response); free(ctx); } /* Returns the user-supplied state value */ void *evapp_get_state(struct evapp_context *ctx) { return ctx->state; } bool evapp_set_state(struct evapp_context *ctx, void *state) { ctx->state = state; return true; } const char *evapp_get_default_domain(struct evapp_context *ctx) { return ctx->default_domain; } bool evapp_set_default_domain(struct evapp_context *ctx, const char *domain) { if (ctx->default_domain) free(ctx->default_domain); ctx->default_domain = strdup(domain); if (!ctx->default_domain) return false; return true; } bool evapp_get_debug_mode(struct evapp_context *ctx) { return ctx->debug_mode; } bool evapp_set_debug_mode(struct evapp_context *ctx, bool debug) { ctx->debug_mode = debug; return true; } bool evapp_get_default_templates(struct evapp_context *ctx) { return ctx->default_templates; } bool evapp_set_default_templates(struct evapp_context *ctx, bool value) { ctx->default_templates = value; return true; } bool evapp_get_xsrf_protection(struct evapp_context *ctx) { return ctx->xsrf_protection; } bool evapp_set_xsrf_protection(struct evapp_context *ctx, bool value) { ctx->xsrf_protection = value; return true; } bool evapp_get_scaffold(struct evapp_context *ctx) { return ctx->scaffold; } bool evapp_set_scaffold(struct evapp_context *ctx, bool value) { ctx->scaffold = value; return true; } struct evbuffer *evapp_response(struct evapp_context *ctx) { if (!ctx) return NULL; return ctx->response; } struct evhttp_request *evapp_request(struct evapp_context *ctx) { if (!ctx) return NULL; return ctx->request; }