@ -1,34 +1,46 @@
|
||||
#include <ctorm/log.h>
|
||||
#include <ctorm/ctorm.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
option_t options[] = {
|
||||
{"host", "0.0.0.0:7003", true }, // host the server should listen on
|
||||
{NULL, NULL, false},
|
||||
{"host", "0.0.0.0:7003", true }, // host the server should listen on
|
||||
{"docs_dir", "./docs", true }, // documentation directory
|
||||
{"", NULL, false},
|
||||
};
|
||||
|
||||
int32_t config_load(config_t *conf) {
|
||||
bool config_load(config_t *conf) {
|
||||
bzero(conf, sizeof(*conf));
|
||||
|
||||
char *value = NULL;
|
||||
char name_env[OPT_NAME_MAX + 5], name_copy[OPT_NAME_MAX], *value = NULL;
|
||||
conf->options = options;
|
||||
|
||||
for (option_t *opt = conf->options; opt->name != NULL; opt++) {
|
||||
if ((value = getenv(opt->name)) != NULL)
|
||||
for (option_t *opt = conf->options; opt->value != NULL; opt++, conf->count++) {
|
||||
strcpy(name_copy, opt->name);
|
||||
util_toupper(name_copy);
|
||||
snprintf(name_env, sizeof(name_env), "DOC_%s", name_copy);
|
||||
|
||||
if ((value = getenv(name_env)) != NULL)
|
||||
opt->value = value;
|
||||
|
||||
if (opt->required && *opt->value == 0) {
|
||||
error("please specify a value for the required config option %s", opt->name);
|
||||
errno = EFAULT;
|
||||
return -1;
|
||||
}
|
||||
if (*opt->value == 0)
|
||||
opt->value = NULL;
|
||||
|
||||
conf->count++;
|
||||
if (!opt->required || NULL != opt->value)
|
||||
continue;
|
||||
|
||||
ctorm_fail("please specify a value for the required config option: %s (%s)", opt->name, name_env);
|
||||
errno = EFAULT;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
char *config_get(config_t *conf, const char *name) {
|
||||
|
@ -1,25 +1,41 @@
|
||||
#include <ctorm/all.h>
|
||||
#include <ctorm/ctorm.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "routes.h"
|
||||
#include "config.h"
|
||||
#include "routes.h"
|
||||
|
||||
int main() {
|
||||
config_t conf;
|
||||
app_config_t config;
|
||||
ctorm_app_t *app = NULL;
|
||||
ctorm_config_t app_config;
|
||||
|
||||
if (config_load(&conf) < 0)
|
||||
config_t conf;
|
||||
char *host = NULL;
|
||||
|
||||
if (!config_load(&conf))
|
||||
return EXIT_FAILURE;
|
||||
host = config_get(&conf, "host");
|
||||
|
||||
app_config_new(&config);
|
||||
config.disable_logging = true;
|
||||
ctorm_config_new(&app_config);
|
||||
app_config.disable_logging = true;
|
||||
app = ctorm_app_new(&app_config);
|
||||
|
||||
app_t *app = app_new(&config);
|
||||
GET(app, "/read", GET_read);
|
||||
// middlewares
|
||||
MIDDLEWARE_ALL(app, "/*", route_cors);
|
||||
MIDDLEWARE_ALL(app, "/*/*", route_cors);
|
||||
MIDDLEWARE_ALL(app, "/*/*/*", route_cors);
|
||||
|
||||
if (!app_run(app, config_get(&conf, "host")))
|
||||
error("failed to start the app: %s", app_geterror());
|
||||
// routes
|
||||
GET(app, "/list", route_list);
|
||||
GET(app, "/get/:name", route_get);
|
||||
|
||||
app_free(app);
|
||||
ctorm_app_all(app, route_notfound);
|
||||
ctorm_app_local(app, "config", &conf);
|
||||
|
||||
ctorm_info("starting the web server on %s", host);
|
||||
|
||||
if (!ctorm_app_run(app, host))
|
||||
ctorm_fail("failed to start the app: %s", ctorm_geterror());
|
||||
|
||||
ctorm_app_free(app);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
9
doc/src/routes/cors.c
Normal file
9
doc/src/routes/cors.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include <ctorm/ctorm.h>
|
||||
|
||||
void route_cors(ctorm_req_t *req, ctorm_res_t *res) {
|
||||
RES_SET("Access-Control-Allow-Origin", "*");
|
||||
RES_SET("Access-Control-Allow-Headers",
|
||||
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, "
|
||||
"X-Requested-With");
|
||||
RES_SET("Access-Control-Allow-Methods", "PUT, DELETE, GET");
|
||||
}
|
78
doc/src/routes/get.c
Normal file
78
doc/src/routes/get.c
Normal file
@ -0,0 +1,78 @@
|
||||
#include <linux/limits.h>
|
||||
#include <ctorm/ctorm.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "routes.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
void route_get(ctorm_req_t *req, ctorm_res_t *res) {
|
||||
config_t *conf = REQ_LOCAL("config");
|
||||
char *docs_dir = config_get(conf, "docs_dir");
|
||||
const char *name = REQ_PARAM("name");
|
||||
|
||||
if (NULL == name) {
|
||||
ctorm_fail("doc name is not specified");
|
||||
return util_send(res, 500, NULL);
|
||||
}
|
||||
|
||||
if (strlen(name) > NAME_MAX - 6)
|
||||
return util_send(res, 404, NULL);
|
||||
|
||||
char full_path[PATH_MAX + 1], full_name[NAME_MAX + 1];
|
||||
util_file_t *doc_file = NULL, *json_file = NULL;
|
||||
cJSON *doc_json = NULL;
|
||||
|
||||
// read the doc markdown
|
||||
snprintf(full_name, sizeof(full_name), "%s.md", name);
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", docs_dir, full_name);
|
||||
|
||||
if (!util_dir_contains(docs_dir, full_name)) {
|
||||
util_send(res, 404, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (NULL == (doc_file = util_file_load(full_path))) {
|
||||
ctorm_fail("failed to load file: %s", full_path);
|
||||
util_send(res, 500, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
// read the doc JSON
|
||||
snprintf(full_name, sizeof(full_name), "%s.json", name);
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", docs_dir, full_name);
|
||||
|
||||
if (!util_dir_contains(docs_dir, full_name)) {
|
||||
util_send(res, 404, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (NULL == (json_file = util_file_load(full_path))) {
|
||||
ctorm_fail("failed to load file: %s", full_path);
|
||||
util_send(res, 500, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
// parse the doc JSON
|
||||
if (NULL == (doc_json = cJSON_Parse(json_file->content))) {
|
||||
ctorm_fail("failed to parse file: %s", full_path);
|
||||
util_send(res, 500, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(doc_json, "content", doc_file->content);
|
||||
util_send(res, 200, doc_json);
|
||||
|
||||
end:
|
||||
if (NULL != doc_file)
|
||||
util_file_free(doc_file);
|
||||
|
||||
if (NULL != json_file)
|
||||
util_file_free(json_file);
|
||||
|
||||
if (NULL != doc_json)
|
||||
cJSON_free(doc_json);
|
||||
}
|
72
doc/src/routes/list.c
Normal file
72
doc/src/routes/list.c
Normal file
@ -0,0 +1,72 @@
|
||||
#include <linux/limits.h>
|
||||
#include <ctorm/ctorm.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
|
||||
void route_list(ctorm_req_t *req, ctorm_res_t *res) {
|
||||
config_t *conf = REQ_LOCAL("config");
|
||||
char *docs_dir = config_get(conf, "docs_dir");
|
||||
|
||||
cJSON *array = NULL, *json = NULL;
|
||||
DIR *docs_dir_fd = NULL;
|
||||
|
||||
if (NULL == (array = cJSON_CreateArray())) {
|
||||
ctorm_fail("failed to create cJSON array");
|
||||
return util_send(res, 500, NULL);
|
||||
}
|
||||
|
||||
if (NULL == (docs_dir_fd = opendir(docs_dir))) {
|
||||
ctorm_fail("failed to open the docs dir (%s): %s", docs_dir, ctorm_geterror());
|
||||
return util_send(res, 500, NULL);
|
||||
}
|
||||
|
||||
char doc_path[PATH_MAX + 1], doc_name[NAME_MAX + 1];
|
||||
util_file_t *doc_file = NULL;
|
||||
struct dirent *doc = NULL;
|
||||
uint64_t ext_indx = 0;
|
||||
|
||||
while (NULL != (doc = readdir(docs_dir_fd))) {
|
||||
if ((ext_indx = util_endswith(doc->d_name, ".json")) == 0)
|
||||
continue;
|
||||
|
||||
snprintf(doc_path, sizeof(doc_path), "%s/%s", docs_dir, doc->d_name);
|
||||
|
||||
if (NULL == (doc_file = util_file_load(doc_path))) {
|
||||
ctorm_fail("failed to load the JSON file: %s", doc_path);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (NULL == (json = cJSON_Parse(doc_file->content))) {
|
||||
ctorm_fail("failed to parse the JSON file: %s", doc_path);
|
||||
goto next;
|
||||
}
|
||||
|
||||
strcpy(doc_name, doc->d_name);
|
||||
util_tolower(doc_name);
|
||||
doc_name[ext_indx] = 0;
|
||||
|
||||
cJSON_AddStringToObject(json, "name", doc_name);
|
||||
cJSON_AddItemToArray(array, json);
|
||||
|
||||
next:
|
||||
if (NULL != doc_file)
|
||||
util_file_free(doc_file);
|
||||
doc_file = NULL;
|
||||
}
|
||||
|
||||
closedir(docs_dir_fd);
|
||||
|
||||
if (NULL == (json = cJSON_CreateObject())) {
|
||||
ctorm_fail("failed to create cJSON object");
|
||||
return util_send(res, 500, NULL);
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(json, "list", array);
|
||||
util_send(res, 200, json);
|
||||
cJSON_free(json);
|
||||
}
|
6
doc/src/routes/notfound.c
Normal file
6
doc/src/routes/notfound.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include "routes.h"
|
||||
#include "util.h"
|
||||
|
||||
void route_notfound(ctorm_req_t *req, ctorm_res_t *res) {
|
||||
return util_send(res, 404, NULL);
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
#include <linux/limits.h>
|
||||
#include <ctorm/all.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <cmark.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "routes.h"
|
||||
|
||||
void GET_read(req_t *req, res_t *res) {
|
||||
const char *lang = REQ_QUERY("lang");
|
||||
const char *name = REQ_QUERY("name");
|
||||
|
||||
char fp[PATH_MAX + 1], md[NAME_MAX + 1];
|
||||
struct dirent *dirent = NULL;
|
||||
int32_t name_len = 0, ent_len = 0;
|
||||
DIR *dir = NULL;
|
||||
|
||||
bzero(fp, sizeof(fp));
|
||||
bzero(md, sizeof(md));
|
||||
|
||||
if (NULL == name)
|
||||
return RES_REDIRECT("/");
|
||||
|
||||
name_len = strlen(name);
|
||||
|
||||
if (NULL == lang)
|
||||
lang = "en";
|
||||
|
||||
if (NULL == (dir = opendir("docs"))) {
|
||||
error("failed to open the docs dir: %s", strerror(errno));
|
||||
RES_SENDFILE("html/internal.html");
|
||||
res->code = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dirent = readdir(dir)) != NULL) {
|
||||
if (strncmp(dirent->d_name, lang, PATH_MAX) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (NULL == dirent) {
|
||||
RES_SENDFILE("html/notfound.html");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(fp, sizeof(fp), "docs/%s", lang);
|
||||
|
||||
if (NULL == (dir = opendir(fp))) {
|
||||
error("failed to open the language dir: %s", strerror(errno));
|
||||
RES_SENDFILE("html/internal.html");
|
||||
res->code = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dirent = readdir(dir)) != NULL) {
|
||||
if ((ent_len = strlen(dirent->d_name) - 3) != name_len)
|
||||
continue;
|
||||
|
||||
if (strncmp(dirent->d_name, name, name_len) == 0) {
|
||||
memcpy(md, dirent->d_name, ent_len + 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (NULL == dirent) {
|
||||
RES_SENDFILE("html/notfound.html");
|
||||
return;
|
||||
}
|
||||
|
||||
char *md_content = NULL, md_fp[PATH_MAX + 1];
|
||||
struct stat md_st;
|
||||
int md_fd = 0;
|
||||
|
||||
snprintf(md_fp, sizeof(fp), "%s/%s", fp, md);
|
||||
|
||||
if ((md_fd = open(md_fp, O_RDONLY)) < 0) {
|
||||
error("failed to open %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
if (fstat(md_fd, &md_st) < 0) {
|
||||
error("failed to fstat %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
if ((md_content = mmap(0, md_st.st_size, PROT_READ, MAP_PRIVATE, md_fd, 0)) == NULL) {
|
||||
error("failed to mmap %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
char *parsed = cmark_markdown_to_html(md_content, md_st.st_size, CMARK_OPT_DEFAULT);
|
||||
RES_SEND(parsed);
|
||||
|
||||
free(parsed);
|
||||
munmap(md_content, md_st.st_size);
|
||||
close(md_fd);
|
||||
|
||||
return;
|
||||
|
||||
err_internal_close:
|
||||
close(md_fd);
|
||||
|
||||
RES_SENDFILE("html/internal.html");
|
||||
return;
|
||||
}
|
125
doc/src/util.c
Normal file
125
doc/src/util.c
Normal file
@ -0,0 +1,125 @@
|
||||
#include <linux/limits.h>
|
||||
#include <ctorm/ctorm.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
uint64_t util_endswith(char *str, char *suf) {
|
||||
if (NULL == str || NULL == suf) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t sufl = strlen(suf);
|
||||
uint64_t strl = strlen(str);
|
||||
|
||||
if (sufl > strl)
|
||||
return false;
|
||||
|
||||
uint64_t indx = strl - sufl;
|
||||
|
||||
return strncmp(str + indx, suf, sufl) == 0 ? indx : 0;
|
||||
}
|
||||
|
||||
void util_send(ctorm_res_t *res, uint16_t code, cJSON *json) {
|
||||
if (NULL == json)
|
||||
json = cJSON_CreateObject();
|
||||
const char *error = "";
|
||||
|
||||
switch (code) {
|
||||
case 404:
|
||||
error = "not found";
|
||||
break;
|
||||
|
||||
case 400:
|
||||
error = "bad request";
|
||||
break;
|
||||
|
||||
case 500:
|
||||
error = "internal server error";
|
||||
break;
|
||||
}
|
||||
|
||||
if (*error != 0)
|
||||
cJSON_AddStringToObject(json, "error", error);
|
||||
|
||||
RES_JSON(json);
|
||||
}
|
||||
|
||||
bool util_dir_contains(char *dir, const char *file) {
|
||||
if (NULL == dir || NULL == file) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent *dirent = NULL;
|
||||
DIR *dirfd = NULL;
|
||||
|
||||
if (NULL == (dirfd = opendir(dir)))
|
||||
return false; // errno set by opendir
|
||||
|
||||
while ((dirent = readdir(dirfd)) != NULL) {
|
||||
if (util_compare_name(dirent->d_name, ".") || util_compare_name(dirent->d_name, ".."))
|
||||
continue;
|
||||
|
||||
if (util_compare_name(dirent->d_name, file))
|
||||
break;
|
||||
}
|
||||
|
||||
closedir(dirfd);
|
||||
return NULL != dirent;
|
||||
}
|
||||
|
||||
util_file_t *util_file_load(char *path) {
|
||||
if (NULL == path) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
util_file_t *file = NULL;
|
||||
struct stat buf;
|
||||
int fd = -1;
|
||||
|
||||
if (NULL == (file = malloc(sizeof(util_file_t))))
|
||||
goto end; // errno set by malloc
|
||||
|
||||
if ((fd = open(path, O_RDONLY)) < 0)
|
||||
goto end; // errno set by open
|
||||
|
||||
if (fstat(fd, &buf) < 0)
|
||||
goto end; // errno set by fstat
|
||||
|
||||
if (NULL == (file->content = mmap(0, (file->size = buf.st_size), PROT_READ, MAP_PRIVATE, fd, 0)))
|
||||
goto end; // errno set by mmap
|
||||
|
||||
end:
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
|
||||
if (NULL == file->content) {
|
||||
free(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void util_file_free(util_file_t *file) {
|
||||
if (NULL == file)
|
||||
return;
|
||||
|
||||
munmap(file->content, file->size);
|
||||
free(file);
|
||||
}
|
Reference in New Issue
Block a user