rest server wip
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
NADAL Jean-Baptiste
2020-02-18 17:56:15 +01:00
parent cf9decf7b8
commit c7b9e005e8
12 changed files with 681 additions and 83 deletions

View File

@@ -35,12 +35,25 @@
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <evhttp.h>
#include <event2/http.h>
#include <event2/listener.h>
#ifdef __linux__
#include <sys/eventfd.h>
#else
#include <sys/event.h>
#endif
#include "macro.h"
#include "restd.h"
struct mimetype_s
{
const char *extn;
const char *mime;
};
/*
* Local variables.
*/
@@ -51,6 +64,77 @@ static bool initialized = false;
*/
int _restd_log_level = RESTD_LOG_WARN;
/*--------------------------------------------------------------------------*/
static const struct mimetype_s g_mime_types[] = {
{"txt", "text/plain"},
{"log", "text/plain"},
{"js", "text/javascript"},
{"css", "text/css"},
{"htm", "text/html"},
{"html", "text/html"},
{"diff", "text/x-patch"},
{"patch", "text/x-patch"},
{"c", "text/x-csrc"},
{"h", "text/x-chdr"},
{"o", "text/x-object"},
{"ko", "text/x-object"},
{"bmp", "image/bmp"},
{"gif", "image/gif"},
{"png", "image/png"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"svg", "image/svg+xml"},
{"json", "application/json"},
{"jsonp", "application/javascript"},
{"zip", "application/zip"},
{"pdf", "application/pdf"},
{"xml", "application/xml"},
{"xsl", "application/xml"},
{"doc", "application/msword"},
{"ppt", "application/vnd.ms-powerpoint"},
{"xls", "application/vnd.ms-excel"},
{"odt", "application/vnd.oasis.opendocument.text"},
{"odp", "application/vnd.oasis.opendocument.presentation"},
{"pl", "application/x-perl"},
{"sh", "application/x-shellscript"},
{"php", "application/x-php"},
{"deb", "application/x-deb"},
{"iso", "application/x-cd-image"},
{"tar.gz", "application/x-compressed-tar"},
{"tgz", "application/x-compressed-tar"},
{"gz", "application/x-gzip"},
{"tar.bz2", "application/x-bzip-compressed-tar"},
{"tbz", "application/x-bzip-compressed-tar"},
{"bz2", "application/x-bzip"},
{"tar", "application/x-tar"},
{"rar", "application/x-rar-compressed"},
{"mp3", "audio/mpeg"},
{"ogg", "audio/x-vorbis+ogg"},
{"wav", "audio/x-wav"},
{"mpg", "video/mpeg"},
{"mpeg", "video/mpeg"},
{"avi", "video/x-msvideo"},
{"README", "text/plain"},
{"log", "text/plain"},
{"cfg", "text/plain"},
{"conf", "text/plain"},
{"pac", "application/x-ns-proxy-autoconfig"},
{"wpad.dat", "application/x-ns-proxy-autoconfig"},
{"appcache", "text/cache-manifest"},
{"manifest", "text/cache-manifest"},
{NULL, NULL}};
/*--------------------------------------------------------------------------*/
/**
* Server option names and default values.
*/
@@ -85,8 +169,11 @@ static int set_undefined_options(restd_server_t *server);
char *restd_server_get_option(restd_server_t *server, const char *key);
int restd_server_get_option_int(restd_server_t *server, const char *key);
static void close_server(restd_server_t *server);
static void *server_loop(void *instance);
static void libevent_log_cb(int severity, const char *msg);
static int notify_loopexit(restd_server_t *server);
static void notify_cb(struct bufferevent *buffer, void *userdata);
void rest_request_cb(struct evhttp_request *req, void *arg);
/*--------------------------- PUBLIC FUNCTIONS -------------------------------*/
@@ -160,16 +247,16 @@ void restd_server_free(restd_server_t *server)
close_server(server);
}
if (server->evbase)
{
event_base_free(server->evbase);
}
if (server->evhttp)
{
evhttp_free(server->evhttp);
}
if (server->evbase)
{
event_base_free(server->evbase);
}
if (server->options)
{
server->options->free(server->options);
@@ -184,8 +271,6 @@ void restd_server_free(restd_server_t *server)
restd_hook_t *hook;
while ((hook = tbl->popfirst(tbl, NULL)))
{
if (hook->method)
free(hook->method);
if (hook->path)
free(hook->path);
free(hook);
@@ -283,7 +368,8 @@ int restd_server_start(restd_server_t *server)
return -2;
}
#if 0
evhttp_set_gencb(server->evhttp, rest_request_cb, server);
ret = evhttp_bind_socket(server->evhttp, addr, port);
if (ret != 0)
@@ -291,8 +377,41 @@ int restd_server_start(restd_server_t *server)
ERROR("Http bind server addr:%s & port:%d failed!\n", addr, port);
return -3;
}
// Listen
INFO("Listening on %s:%d", addr, port);
// Create a eventfd for notification channel.
#ifdef __linux__
int notifyfd = eventfd(0, 0);
#else
int notifyfd = kqueue();
#endif
return 0;
server->notify_buffer = bufferevent_socket_new(server->evbase, notifyfd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(server->notify_buffer, NULL, notify_cb, NULL, server);
int exitstatus = 0;
if (restd_server_get_option_int(server, "server.thread"))
{
DEBUG("Launching server as a thread.\n");
server->thread = NEW_OBJECT(pthread_t);
pthread_create(server->thread, NULL, &server_loop, (void *)server);
//pthread_detach(server->thread);
}
else
{
int *retval = server_loop(server);
exitstatus = *retval;
free(retval);
close_server(server);
if (restd_server_get_option_int(server, "server.free_on_stop"))
{
restd_server_free(server);
}
}
return exitstatus;
}
/*--------------------------------------------------------------------------*/
@@ -315,12 +434,12 @@ void restd_server_set_option(restd_server_t *server, const char *key, const char
/*--------------------------------------------------------------------------*/
void restd_server_register_hook_on_path(restd_server_t *server, const char *method, const char *path,
void restd_server_register_hook_on_path(restd_server_t *server, enum evhttp_cmd_type method, const char *path,
restd_callback cb, void *userdata)
{
restd_hook_t hook;
bzero((void *)&hook, sizeof(restd_hook_t));
hook.method = (method) ? strdup(method) : NULL;
hook.method = method;
hook.path = (path) ? strdup(path) : NULL;
hook.cb = cb;
hook.userdata = userdata;
@@ -328,6 +447,27 @@ void restd_server_register_hook_on_path(restd_server_t *server, const char *meth
server->hooks->addlast(server->hooks, (void *)&hook, sizeof(restd_hook_t));
}
/*--------------------------------------------------------------------------*/
void restd_http_response(struct evhttp_request *req, int code, const char *contenttype, const char *data)
{
struct evbuffer *resp_buf;
struct evkeyvalq *resp_headers;
resp_buf = evhttp_request_get_output_buffer(req);
resp_headers = evhttp_request_get_output_headers(req);
if (data != NULL)
{
evbuffer_add_printf(resp_buf, "%s", data);
}
evhttp_add_header(resp_headers, "Content-Type", contenttype);
evhttp_send_reply(req, code, NULL ,resp_buf);
}
/*--------------------------- LOCAL FUNCTIONS -------------------------------*/
/*--------------------------------------------------------------------------*/
@@ -398,6 +538,21 @@ static void close_server(restd_server_t *server)
/*--------------------------------------------------------------------------*/
static void *server_loop(void *instance)
{
restd_server_t *server = (restd_server_t *)instance;
int *retval = NEW_OBJECT(int);
DEBUG("Loop start\n");
event_base_loop(server->evbase, 0);
DEBUG("Loop finished\n");
*retval = (event_base_got_break(server->evbase)) ? -1 : 0;
return retval;
}
/*--------------------------------------------------------------------------*/
static void libevent_log_cb(int severity, const char *msg)
{
switch (severity)
@@ -435,3 +590,271 @@ static int notify_loopexit(restd_server_t *server)
uint64_t x = 0;
return bufferevent_write(server->notify_buffer, &x, sizeof(uint64_t));
}
/*--------------------------------------------------------------------------*/
static void notify_cb(struct bufferevent *buffer, void *userdata)
{
restd_server_t *server = (restd_server_t *)userdata;
event_base_loopexit(server->evbase, NULL);
DEBUG("Existing loop.");
}
/*--------------------------------------------------------------------------*/
static const char *file_mime_lookup(const char *path)
{
const struct mimetype_s *m = &g_mime_types[0];
const char *e;
while (m->extn)
{
e = &path[strlen(path) - 1];
while (e >= path)
{
if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
return m->mime;
e--;
}
m++;
}
return "application/octet-stream";
}
/*--------------------------------------------------------------------------*/
static bool contain(const char *src, const char *dest, int len)
{
int len_src, len_dest;
int i = 0;
len_src = strlen(src);
if (len_src < len)
return false;
len_dest = strlen(dest);
if (len_dest < len)
return false;
while (i < len)
{
if (src[i] != dest[i])
return false;
i++;
}
return true;
}
/*--------------------------------------------------------------------------*/
void rest_request_cb(struct evhttp_request *req, void *arg)
{
const char *cmdtype;
restd_server_t *server = (restd_server_t *)arg;
printf("request.\n");
switch (evhttp_request_get_command(req))
{
case EVHTTP_REQ_GET:
cmdtype = "GET";
break;
case EVHTTP_REQ_POST:
cmdtype = "POST";
break;
case EVHTTP_REQ_HEAD:
cmdtype = "HEAD";
break;
case EVHTTP_REQ_PUT:
cmdtype = "PUT";
break;
case EVHTTP_REQ_DELETE:
cmdtype = "DELETE";
break;
case EVHTTP_REQ_OPTIONS:
cmdtype = "OPTIONS";
break;
case EVHTTP_REQ_TRACE:
cmdtype = "TRACE";
break;
case EVHTTP_REQ_CONNECT:
cmdtype = "CONNECT";
break;
case EVHTTP_REQ_PATCH:
cmdtype = "PATCH";
break;
default:
cmdtype = "unknown";
break;
}
printf("Received a %s request for %s\nHeaders:\n",
cmdtype, evhttp_request_get_uri(req));
qlist_t *hooks = server->hooks;
//int reason = RESTD_ERROR_PATH_NOT_FOUND;
qlist_obj_t obj;
bzero((void *)&obj, sizeof(qlist_obj_t));
while (hooks->getnext(hooks, &obj, false) == true)
{
restd_hook_t *hook = (restd_hook_t *)obj.data;
if (hook->cb)
{
const char *request_path = evhttp_request_get_uri(req);
printf("==== call_hooks: method: %d - %d \n", hook->method, evhttp_request_get_command(req));
printf("==== call_hooks: path: %s - %s\n", hook->path, request_path);
printf("==== HOOK FOUND !!!!\n");
if (hook->method != evhttp_request_get_command(req))
{
printf("==== Hook found but method failed -> next.\n");
//reason = RESTD_ERROR_METHOD_NOT_ALLOWED;
continue;
}
if ((hook->path != NULL) && (request_path != NULL))
{
int i = 0;
int pos = -1;
while (hook->path[i])
{
if (hook->path[i] == ':')
pos = i;
i++;
}
if (pos != -1 && contain(hook->path, request_path, pos))
{
const char *buffer = &request_path[pos];
// printf("buffer: <%s>\n", buffer);
// TODO conn->id = atoi(buffer);
hook->cb(req, hook->userdata);
return;
}
else
{
int rett = strcmp(hook->path, request_path);
if (rett == 0)
{
hook->cb(req, hook->userdata);
return;
}
}
}
}
}
evhttp_send_reply(req, 200, "OK", NULL);
}
#if 0
int restd_rest_handler(short event, restd_conn_t *conn)
{
if (event & RESTD_EVENT_INIT)
{
DEBUG("==> HTTP INIT");
restd_http_t *http = http_new(conn->out);
if (http == NULL)
return RESTD_CLOSE;
restd_conn_set_extra(conn, http, http_free_cb);
return RESTD_OK;
}
else if (event & RESTD_EVENT_CLOSE)
{
DEBUG("==> HTTP CLOSE=%x (TIMEOUT=%d, SHUTDOWN=%d)",
event, event & RESTD_EVENT_TIMEOUT, event & RESTD_EVENT_SHUTDOWN);
return RESTD_OK;
}
else if ((event & RESTD_EVENT_READ) || (event & RESTD_EVENT_WRITE))
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
int status = http_parser(http, conn->in);
if (conn->method == NULL && http->request.method != NULL)
{
restd_conn_set_method(conn, http->request.method);
}
DEBUG("==> HTTP READ || HTTP WRITE");
int reason = RESTD_ERROR_PATH_NOT_FOUND;
DEBUG("********restd_rest_handler: event 0x%x", event);
char *root_path;
qlist_t *hooks = conn->server->hooks;
qlist_obj_t obj;
bzero((void *)&obj, sizeof(qlist_obj_t));
while (hooks->getnext(hooks, &obj, false) == true)
{
restd_hook_t *hook = (restd_hook_t *)obj.data;
if (hook->cb)
{
printf("==== call_hooks: method: %s - %s \n", hook->method, conn->method);
printf("==== call_hooks: path: %s - %s\n", hook->path, http->request.path);
printf("==== HOOK FOUND !!!!\n");
if ((hook->method == NULL) || (conn->method == NULL) || (strcmp(hook->method, conn->method) != 0))
{
printf("==== Hook found but method failed -> next.\n");
reason = RESTD_ERROR_METHOD_NOT_ALLOWED;
continue;
}
if ((hook->path != NULL) && (http->request.path != NULL))
{
int i = 0;
int pos = -1;
while (hook->path[i])
{
if (hook->path[i] == ':')
pos = i;
i++;
}
if (pos != -1 && contain(hook->path, http->request.path, pos))
{
const char *buffer = &http->request.path[pos];
// printf("buffer: <%s>\n", buffer);
conn->id = atoi(buffer);
return hook->cb(event, conn, hook->userdata);
}
else
{
int rett = strcmp(hook->path, http->request.path);
if (rett == 0)
return hook->cb(event, conn, hook->userdata);
}
}
}
}
// No Hook Found check if it's a real file into document root.
root_path = restd_server_get_option(conn->server, "server.root_path");
if ((root_path != NULL) && (strlen(root_path) != 0))
{
FILE *file;
char buf[1024] = "";
qstrcatf(buf, "%s%s", root_path, http->request.path);
if ((file = fopen(buf, "r")))
{
long fsize;
char *file_buf;
fseek(file, 0, SEEK_END);
fsize = ftell(file);
fseek(file, 0, SEEK_SET);
file_buf = malloc(fsize + 1);
fread(file_buf, 1, fsize, file);
fclose(file);
file_buf[fsize] = 0;
printf("mime type:%s\n", file_mime_lookup(buf));
restd_http_response(conn, 200, file_mime_lookup(buf), file_buf, fsize);
return RESTD_CLOSE;
}
}
if (conn->server->error_handler != NULL)
return conn->server->error_handler(reason, conn, NULL);
return RESTD_CLOSE;
}
BUG_EXIT();
return RESTD_CLOSE;
}
#endif