Steady progress

Added static content serving with sane content type detection.

Started working on WebSockets (meanwhile neither SCGI or FastCGI is
finished and almost nothing has been tested).
This commit is contained in:
Přemysl Eric Janouch 2015-03-08 09:41:10 +01:00
parent 9e0c9dd6d8
commit 931fc441f6
6 changed files with 206 additions and 12 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "liberty"]
path = liberty
url = git://github.com/pjanouch/liberty.git
[submodule "http-parser"]
path = http-parser
url = https://github.com/joyent/http-parser.git

View File

@ -23,9 +23,12 @@ set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package (PkgConfig REQUIRED)
pkg_check_modules (dependencies REQUIRED jansson)
find_package (LibEV REQUIRED)
find_package (LibMagic REQUIRED)
set (project_libraries ${dependencies_LIBRARIES} ${LIBEV_LIBRARIES})
include_directories (${dependencies_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
set (project_libraries ${dependencies_LIBRARIES}
${LIBEV_LIBRARIES} ${LIBMAGIC_LIBRARIES})
include_directories (${dependencies_INCLUDE_DIRS}
${LIBEV_INCLUDE_DIRS} ${LIBMAGIC_INCLUDE_DIRS})
# Generate a configuration file
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)

9
README
View File

@ -2,13 +2,15 @@ acid
====
`acid' is A Continuous Integration Daemon. Currently under heavy development.
Right now I'm working on a demo JSON-RPC server that will serve as the basis for
the final daemon.
The aim of this project is to provide a dumbed-down alternative to Travis CI.
I find it way too complex to set up and run in a local setting, while the basic
gist of it is actually very simple -- run some stuff on new git commits.
`acid' will provide a JSON-RPC 2.0 service for frontends over FastCGI or SCGI,
as well as a webhook endpoint for notifications about new commits.
`acid' will provide a JSON-RPC 2.0 service for frontends over FastCGI, SCGI, or
WebSockets, as well as a webhook endpoint for notifications about new commits.
`acid' will be able to tell you about build results via e-mail and/or IRC.
@ -21,7 +23,8 @@ in a somewhat clean manner. Feel free to contribute.
Building and Installing
-----------------------
Build dependencies: CMake, pkg-config, help2man, liberty (included)
Build dependencies: CMake, pkg-config, help2man, libmagic,
liberty (included), http-parser (included)
Runtime dependencies: libev, Jansson
$ git clone https://github.com/pjanouch/acid.git

6
cmake/FindLibMagic.cmake Normal file
View File

@ -0,0 +1,6 @@
# Public Domain
find_library (LIBMAGIC_LIBRARIES magic)
find_path (LIBMAGIC_INCLUDE_DIRS magic.h)
find_package_handle_standard_args (LibMagic DEFAULT_MSG
LIBMAGIC_LIBRARIES LIBMAGIC_INCLUDE_DIRS)

View File

@ -34,6 +34,7 @@
#include <ev.h>
#include <jansson.h>
#include <magic.h>
// --- Extensions to liberty ---------------------------------------------------
@ -733,7 +734,7 @@ fcgi_muxer_on_abort_request
return;
}
// TODO: abort the request
// TODO: abort the request: let it somehow produce FCGI_END_REQUEST
}
static void
@ -995,6 +996,8 @@ static struct config_item g_config_table[] =
{ "bind_host", NULL, "Address of the server" },
{ "port_fastcgi", "9000", "Port to bind for FastCGI" },
{ "port_scgi", NULL, "Port to bind for SCGI" },
{ "port_ws", NULL, "Port to bind for WebSockets" },
{ "static_root", NULL, "The root for static content" },
{ NULL, NULL, NULL }
};
@ -1146,9 +1149,6 @@ json_rpc_ping (struct server_context *ctx, json_t *params)
static json_t *
process_json_rpc_request (struct server_context *ctx, json_t *request)
{
// TODO: takes a parsed JSON request and returns back a JSON reply.
// This function may get called multiple times for batch requests.
if (!json_is_object (request))
return json_rpc_response (NULL, NULL,
json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
@ -1183,7 +1183,6 @@ process_json_rpc_request (struct server_context *ctx, json_t *request)
return response;
// Notifications don't get responses
// TODO: separate notifications from non-notifications?
json_decref (response);
return NULL;
}
@ -1320,7 +1319,8 @@ request_start (struct request *self, struct str_map *headers)
// Unable to serve the request
struct str response;
str_init (&response);
str_append (&response, "404 Not Found\r\n\r\n");
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
self->write_cb (self->user_data, response.str, response.len);
str_free (&response);
return false;
@ -1393,7 +1393,147 @@ struct request_handler g_request_handler_json_rpc =
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// TODO: another request handler to respond to all GETs with a message
// TODO: refactor this spaghetti-tier code
static bool
request_handler_static_try_handle
(struct request *request, struct str_map *headers)
{
struct server_context *ctx = request->ctx;
const char *root = str_map_find (&ctx->config, "static_root");
if (!root)
{
print_debug ("static document root not configured");
return false;
}
const char *method = str_map_find (headers, "REQUEST_METHOD");
if (!method || strcmp (method, "GET"))
return false;
// TODO: look at <SCRIPT_NAME, PATH_INFO>, REQUEST_URI in the headers
const char *path_info = str_map_find (headers, "PATH_INFO");
if (!path_info)
{
print_debug ("PATH_INFO not defined");
return false;
}
struct str_vector v;
str_vector_init (&v);
split_str_ignore_empty (path_info, '/', &v);
struct str_vector resolved;
str_vector_init (&resolved);
// So that the joined path begins with a slash
str_vector_add (&resolved, "");
// We need to filter the path to stay in our root
// Being able to read /etc/passwd would be rather embarrasing
for (size_t i = 0; i < v.len; i++)
{
const char *dir = v.vector[i];
if (!strcmp (dir, "."))
continue;
if (strcmp (dir, ".."))
str_vector_add (&resolved, dir);
else if (resolved.len)
str_vector_remove (&resolved, resolved.len - 1);
}
str_vector_free (&v);
char *suffix = join_str_vector (&resolved, '/');
str_vector_free (&resolved);
char *path = xstrdup_printf ("%s%s", root, suffix);
FILE *fp = fopen (path, "rb");
if (!fp)
{
struct str response;
str_init (&response);
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
str_append_printf (&response,
"File %s was not found on this server\n", suffix);
request->write_cb (request->user_data, response.str, response.len);
str_free (&response);
free (suffix);
free (path);
return false;
}
free (suffix);
free (path);
uint8_t buf[8192];
size_t len;
// Try to detect the Content-Type from the actual contents
char *mime_type = NULL;
if ((len = fread (buf, 1, sizeof buf, fp)))
{
magic_t cookie;
const char *magic = NULL;
if ((cookie = magic_open (MAGIC_MIME)))
{
if (!magic_load (cookie, NULL)
&& (magic = magic_buffer (cookie, buf, len)))
mime_type = xstrdup (magic);
magic_close (cookie);
}
}
if (!mime_type)
{
print_debug ("MIME type detection failed");
mime_type = xstrdup ("application/octet_stream");
}
struct str response;
str_init (&response);
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
request->write_cb (request->user_data, response.str, response.len);
str_free (&response);
free (mime_type);
// Write the chunk we've used to help us with magic detection;
// obviously we have to do it after we've written the headers
if (len)
request->write_cb (request->user_data, buf, len);
while ((len = fread (buf, 1, sizeof buf, fp)))
request->write_cb (request->user_data, buf, len);
fclose (fp);
return true;
}
static bool
request_handler_static_push
(struct request *request, const void *data, size_t len)
{
(void) request;
(void) data;
(void) len;
// Ignoring all content; we shouldn't receive any (GET)
return false;
}
static void
request_handler_static_destroy (struct request *request)
{
(void) request;
}
struct request_handler g_request_handler_static =
{
.try_handle = request_handler_static_try_handle,
.push_cb = request_handler_static_push,
.destroy_cb = request_handler_static_destroy,
};
// --- Client communication handlers -------------------------------------------
@ -1673,6 +1813,35 @@ static struct client_impl g_client_scgi =
.push = client_scgi_push,
};
// - - WebSockets - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
client_ws_init (struct client *client)
{
// TODO
}
static void
client_ws_destroy (struct client *client)
{
// TODO
}
static bool
client_ws_push (struct client *client, const void *data, size_t len)
{
// TODO: first push everything into a http_parser, then after a protocol
// upgrade start parsing the WebSocket frames themselves
}
static struct client_impl g_client_ws =
{
.init = client_ws_init,
.destroy = client_ws_destroy,
.push = client_ws_push,
};
// --- Basic server stuff ------------------------------------------------------
struct listener
@ -1874,6 +2043,7 @@ setup_listen_fds (struct server_context *ctx, struct error **e)
const char *bind_host = str_map_find (&ctx->config, "bind_host");
const char *port_fcgi = str_map_find (&ctx->config, "port_fastcgi");
const char *port_scgi = str_map_find (&ctx->config, "port_scgi");
const char *port_ws = str_map_find (&ctx->config, "port_ws");
struct addrinfo gai_hints;
memset (&gai_hints, 0, sizeof gai_hints);
@ -1883,11 +2053,14 @@ setup_listen_fds (struct server_context *ctx, struct error **e)
struct str_vector ports_fcgi; str_vector_init (&ports_fcgi);
struct str_vector ports_scgi; str_vector_init (&ports_scgi);
struct str_vector ports_ws; str_vector_init (&ports_ws);
if (port_fcgi)
split_str_ignore_empty (port_fcgi, ',', &ports_fcgi);
if (port_scgi)
split_str_ignore_empty (port_scgi, ',', &ports_scgi);
if (port_ws)
split_str_ignore_empty (port_ws, ',', &ports_ws);
size_t n_ports = ports_fcgi.len + ports_scgi.len;
ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners);
@ -1898,9 +2071,13 @@ setup_listen_fds (struct server_context *ctx, struct error **e)
for (size_t i = 0; i < ports_scgi.len; i++)
listener_add (ctx, bind_host, ports_scgi.vector[i],
&gai_hints, &g_client_scgi);
for (size_t i = 0; i < ports_ws.len; i++)
listener_add (ctx, bind_host, ports_ws.vector[i],
&gai_hints, &g_client_ws);
str_vector_free (&ports_fcgi);
str_vector_free (&ports_scgi);
str_vector_free (&ports_ws);
if (!ctx->n_listeners)
{
@ -2040,6 +2217,7 @@ main (int argc, char *argv[])
(void) signal (SIGPIPE, SIG_IGN);
LIST_PREPEND (ctx.handlers, &g_request_handler_static);
LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc);
if (!parse_config (&ctx, &e)

1
http-parser Submodule

@ -0,0 +1 @@
Subproject commit d547f3b1a98ed07fdcdaf401a8cbc5fffe9bfa6c