Browse Source

Add dml_httpd

Add htdocs/index.html with DML javascript and simple listing.
master
Jeroen Vreeken 5 years ago
parent
commit
79f76c6428
5 changed files with 876 additions and 1 deletions
  1. +1
    -0
      .gitignore
  2. +5
    -1
      Makefile
  3. +603
    -0
      dml_httpd.c
  4. +10
    -0
      dml_httpd.conf
  5. +257
    -0
      htdocs/index.html

+ 1
- 0
.gitignore View File

@ -2,6 +2,7 @@
*.o
*.d
dmld
dml_httpd
dml_list
dml_reflector
dml_streamer_ogg


+ 5
- 1
Makefile View File

@ -28,7 +28,7 @@ DML_OBJS = $(DML_SRCS:.c=.o)
TRX_OBJS = $(TRX_SRCS:.c=.o)
ETH_AR_OBJS = $(ETH_AR_SRCS:.c=.o)
all: dmld dml_list dml_reflector dml_streamer_ogg dml_stream_client dml_trx
all: dmld dml_list dml_reflector dml_streamer_ogg dml_stream_client dml_trx dml_httpd
SRCS += $(DML_SRCS) $(TRX_SRCS) $(ETH_AR_SRCS)
@ -51,6 +51,10 @@ dml_streamer_ogg: $(DML_OBJS) dml_streamer_ogg.o
SRCS += dml_stream_client.c
dml_stream_client: $(DML_OBJS) dml_stream_client.o
SRCS += dml_httpd.c
dml_httpd_LDFLAGS += -lwebsockets -lmagic
dml_httpd: $(DML_OBJS) dml_httpd.o
DEPS:=$(SRCS:.c=.d)
-include $(DEPS)


+ 603
- 0
dml_httpd.c View File

@ -0,0 +1,603 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <poll.h>
#include <errno.h>
#include <time.h>
#include <magic.h>
#include <libwebsockets.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "dml_client.h"
#include "dml_config.h"
#include "dml_connection.h"
#include "dml_packet.h"
#include "dml_poll.h"
magic_t magic;
char *cgi_path;
char *htdocs;
unsigned short port;
char *dml_host;
struct libwebsocket_context *lws_context;
struct writebuf {
char *data;
char *msg;
size_t msg_len;
struct writebuf *next;
};
struct ws_client {
struct libwebsocket_context *context;
struct libwebsocket *wsi;
struct writebuf *writeq;
/* dml specific data here */
struct dml_connection *dc;
bool dml_closed;
struct ws_client *next;
};
struct writebuf *writebuf_alloc(size_t msglen)
{
struct writebuf *wb;
wb = malloc(sizeof(struct writebuf));
if (!wb)
return NULL;
wb->data = malloc(msglen +
LWS_SEND_BUFFER_PRE_PADDING +
LWS_SEND_BUFFER_POST_PADDING);
wb->msg = wb->data + LWS_SEND_BUFFER_PRE_PADDING;
return wb;
}
void writebuf_free(struct writebuf *wb)
{
free(wb->data);
free(wb);
}
void writebuf_add(struct ws_client *client, struct writebuf *wb)
{
struct writebuf **entry;
for (entry = &client->writeq; *entry; entry = &(*entry)->next);
*entry = wb;
wb->next = NULL;
}
struct writebuf *writebuf_next(struct ws_client *client)
{
struct writebuf *wb;
if (!client->writeq)
return NULL;
wb = client->writeq;
client->writeq = wb->next;
return wb;
}
struct ws_client *ws_client_list = NULL;
struct ws_client *ws_client_add(struct libwebsocket_context *context, struct libwebsocket *wsi)
{
struct ws_client *client;
client = calloc(sizeof(struct ws_client), 1);
if (!client)
return NULL;
client->context = context;
client->wsi = wsi;
client->next = ws_client_list;
ws_client_list = client;
return client;
}
void ws_client_remove(struct ws_client *client)
{
struct ws_client **entry;
for (entry = &ws_client_list; *entry; entry = &(*entry)->next) {
if (*entry == client) {
struct writebuf *wb;
while ((wb = writebuf_next(client))) {
writebuf_free(wb);
}
dml_connection_destroy(client->dc);
/* printf("close srcp connections\n");
poll_remove(srcp_fd_get((*entry)->srcp_cmd));
poll_remove(srcp_fd_get((*entry)->srcp_info));
srcp_destroy(client->srcp_info);
srcp_destroy(client->srcp_cmd);
*/
*entry = (*entry)->next;
free(client);
return;
}
}
}
struct ws_client *ws_client_get_by_wsi(struct libwebsocket *wsi)
{
struct ws_client *entry;
for (entry = ws_client_list; entry; entry = entry->next)
if (entry->wsi == wsi)
return entry;
printf("wsi %p not found\n", wsi);
return NULL;
}
struct ws_client *ws_client_get_by_dc(struct dml_connection *dc)
{
struct ws_client *entry;
for (entry = ws_client_list; entry; entry = entry->next) {
if (entry->dc == dc)
return entry;
}
printf("dc %p not found\n", dc);
return NULL;
}
void ws_client_flush(struct ws_client *client)
{
while (client->writeq) {
struct writebuf *wb;
if (lws_send_pipe_choked(client->wsi))
break;
wb = writebuf_next(client);
libwebsocket_write(client->wsi, (unsigned char *)wb->msg, wb->msg_len, LWS_WRITE_BINARY);
writebuf_free(wb);
}
if (client->writeq) {
libwebsocket_callback_on_writable(client->context, client->wsi);
}
}
//#define LINEBUF_SIZE 8192
#define READ_SIZE 4096
int exec_cgi(struct libwebsocket *wsi, char *requested_uri, char *resource_path)
{
FILE *fpipe;
unsigned char *outdata = NULL;
size_t pos = 0, r;
char *wd = get_current_dir_name();
chdir(cgi_path);
fpipe = popen(resource_path, "r");
if (!fpipe) {
free(wd);
return -1;
}
do {
outdata = realloc(outdata, pos + READ_SIZE);
r = fread(outdata + pos, 1, READ_SIZE, fpipe);
if (r > 0) {
pos += r;
}
} while (r > 0);
if (!lws_send_pipe_choked(wsi))
libwebsocket_write(wsi, outdata, pos, LWS_WRITE_HTTP);
pclose(fpipe);
free(outdata);
free(wd);
return -1;
}
void rx_packet(struct dml_connection *dc, void *arg,
uint16_t id, uint16_t len, uint8_t *data)
{
uint8_t *msg;
struct ws_client *ws_client;
struct writebuf *wb;
ws_client = ws_client_get_by_dc(dc);
wb = writebuf_alloc(len + 4);
msg = (uint8_t *)wb->msg;
wb->msg_len = len + 4;
memcpy(msg + 4, data, len);
msg[0] = id >> 8;
msg[1] = id & 0xff;
msg[2] = len >> 8;
msg[3] = len & 0xff;
writebuf_add(ws_client, wb);
ws_client_flush(ws_client);
}
int list_dir(struct libwebsocket_context *context, struct libwebsocket *wsi, char *requested_uri, char *resource_path)
{
unsigned char *outdata = malloc(1000);
size_t pos = 0;
struct dirent **namelist;
int n, i;
unsigned char *h = outdata;
char *server = "dml_httpd libwebsockets";
char *type = "text/html";
if (lws_add_http_header_status(context, wsi, 200, &h, outdata + 1000))
return 1;
if (lws_add_http_header_by_token(context, wsi,
WSI_TOKEN_HTTP_SERVER,
(unsigned char *)server, strlen(server), &h, outdata + 1000))
return 1;
if (lws_add_http_header_by_token(context, wsi,
WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)type, strlen(type), &h, outdata + 1000))
return 1;
if (lws_finalize_http_header(context, wsi, &h, outdata + 1000))
return 1;
pos += h - outdata;
libwebsocket_write(wsi, outdata, pos, LWS_WRITE_HTTP_HEADERS);
free(outdata);
outdata = NULL;
pos = 0;
n = scandir(resource_path, &namelist, NULL, alphasort);
for (i = 0; i < n; i++) {
errno = 0;
if (namelist[i]->d_type != DT_REG) {
printf("%s is not a regular file\n", namelist[i]->d_name);
continue;
}
char *line;
asprintf(&line, "<a href='%s'>%s</a><br>\n",
namelist[i]->d_name, namelist[i]->d_name);
outdata = realloc(outdata, pos + strlen(line) + 1);
strcpy((char*)outdata + pos, line);
pos += strlen(line);
free(line);
}
if (n >= 0)
free(namelist);
if (!lws_send_pipe_choked(wsi))
libwebsocket_write(wsi, outdata, pos, LWS_WRITE_HTTP);
free(outdata);
return -1;
}
int client_connection_close(struct dml_connection *dc, void *arg)
{
struct libwebsocket *wsi = arg;
struct ws_client *ws_client;
printf("Connection to DML server closed\n");
ws_client = ws_client_get_by_wsi(wsi);
ws_client->dml_closed = true;
libwebsocket_callback_on_writable(lws_context, wsi);
return 0;
}
void client_connect(struct dml_client *client, void *arg)
{
struct dml_connection *dc;
struct libwebsocket *wsi = arg;
int fd;
struct ws_client *ws_client;
ws_client = ws_client_get_by_wsi(wsi);
fd = dml_client_fd_get(client);
dc = dml_connection_create(fd, arg, rx_packet, client_connection_close);
dml_packet_send_hello(dc, DML_PACKET_HELLO_UPDATES, "dml_httpd " DML_VERSION);
ws_client->dc = dc;
}
int wsi_in_cb(void *arg)
{
// struct libwebsocket *wsi = arg;
libwebsocket_service(lws_context, 0);
return 0;
}
int wsi_out_cb(void *arg)
{
// struct libwebsocket *wsi = arg;
libwebsocket_service(lws_context, 0);
return 0;
}
static int callback_http(struct libwebsocket_context *context,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len)
{
int r = 0;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: {
struct ws_client *ws_client;
printf("lws established\n");
ws_client = ws_client_get_by_wsi(wsi);
if (!ws_client) {
ws_client = ws_client_add(context, wsi);
}
struct dml_client *dc;
dc = dml_client_create(dml_host, 0, client_connect, wsi);
if (dml_client_connect(dc)) {
printf("Could not connect to server\n");
return -1;
}
break;
}
case LWS_CALLBACK_RECEIVE: {
uint8_t *rcv = in;
struct ws_client *ws_client;
printf("lws receive: %zd\n", len);
ws_client = ws_client_get_by_wsi(wsi);
if (!ws_client) {
ws_client = ws_client_add(context, wsi);
}
uint8_t *payload_data = rcv + 4;
ssize_t data_len = len - 4;
uint16_t packet_id = (rcv[0] << 8) | rcv[1];
if (data_len > 0) {
printf("Send packet (id %d, len %zd)\n", packet_id, data_len);
dml_connection_send(ws_client->dc, payload_data, packet_id, data_len);
}
break;
}
case LWS_CALLBACK_CLOSED:
ws_client_remove(ws_client_get_by_wsi(wsi));
printf("Close connection %p\n", wsi);
break;
case LWS_CALLBACK_HTTP: {
char *requested_uri = (char *) in;
int i;
printf("requested URI: %s ", requested_uri);
for (i = 0; strlen(in) > 4 && i < strlen(in) - 3; i++) {
/* Don't go outside the htdocs dir */
if (!strncmp(in + i, "/../", 4))
break;
}
if (strcmp(requested_uri, "/") == 0) {
requested_uri = "/index.html";
}
char *cwd;
cwd = htdocs;
char *resource_path;
struct stat statbuf;
resource_path = malloc(strlen(cwd) + strlen(requested_uri) + 1);
sprintf(resource_path, "%s%s", cwd, requested_uri);
printf("resource path: %s\n", resource_path);
stat(resource_path, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
r = list_dir(context, wsi, requested_uri, resource_path);
} else if (!strcmp(
resource_path + strlen(resource_path) - strlen(".cgi"),
".cgi")) {
r = exec_cgi(wsi, requested_uri, resource_path);
} else {
const char *mime;
if (!strcmp(
resource_path + strlen(resource_path) - strlen(".js"),
".js"))
mime = "application/javascript";
else
mime = magic_file(magic, resource_path);
r = libwebsockets_serve_http_file(context, wsi,
resource_path, mime, NULL, 0);
}
free(resource_path);
break;
}
case LWS_CALLBACK_ADD_POLL_FD: {
struct libwebsocket_pollargs *args = in;
dml_poll_add(wsi, wsi_in_cb, wsi_out_cb, NULL);
dml_poll_fd_set(wsi, args->fd);
dml_poll_in_set(wsi, args->events & POLLIN);
dml_poll_out_set(wsi, args->events & POLLOUT);
break;
}
case LWS_CALLBACK_DEL_POLL_FD: {
dml_poll_remove(wsi);
break;
}
case LWS_CALLBACK_CHANGE_MODE_POLL_FD: {
struct libwebsocket_pollargs *args = in;
dml_poll_fd_set(wsi, args->fd);
dml_poll_in_set(wsi, args->events & POLLIN);
dml_poll_out_set(wsi, args->events & POLLOUT);
break;
}
case LWS_CALLBACK_SERVER_WRITEABLE: {
struct ws_client *ws_client;
ws_client = ws_client_get_by_wsi(wsi);
if (ws_client) {
ws_client_flush(ws_client);
if (ws_client->dml_closed)
r = -1;
}
break;
}
default:
// if (reason != 30)
// printf("unhandled callback (%d)\n", reason);
break;
}
return r;
}
static struct libwebsocket_protocols protocols[] = {
// first protocol must always be HTTP handler
{
"http-only", // name
callback_http, // callback
0 // per_session_data_size
},
{
NULL, NULL, 0 // end of list
}
};
int main(int argc, char **argv)
{
char *file = "dml_httpd.conf";
if (argc > 1)
file = argv[1];
if (dml_config_load(file)) {
printf("Failed to load config file %s\n", file);
return -1;
}
cgi_path = dml_config_value("cgi_path", NULL, ".");
htdocs = dml_config_value("htdocs", NULL, ".");
port = atoi(dml_config_value("port", NULL, "8080"));
dml_host = dml_config_value("dml_host", NULL, "localhost");
magic = magic_open(MAGIC_MIME_TYPE);
if (magic_load(magic, NULL))
printf("magic_load failed\n");
struct lws_context_creation_info creation_info = {
.port = port,
.iface = NULL,
.protocols = protocols,
.gid = -1,
.uid = -1,
};
lws_context = libwebsocket_create_context(&creation_info);
if (lws_context == NULL) {
fprintf(stderr, "libwebsocket init failed\n");
return -1;
}
printf("starting server...\n");
dml_poll_loop();
/* while (1) {
int n;
n = poll(pollfds, count_pollfds, 1000);
if (n < 0)
break;
for (n = 0; n < count_pollfds; n++)
if (pollfds[n].revents) {
// struct ws_client *entry;
*/
/*
for (entry = ws_client_list; entry; entry = entry->next) {
int fdi = srcp_fd_get(entry->srcp_info);
int fdc = srcp_fd_get(entry->srcp_cmd);
if (fdi == pollfds[n].fd) {
srcp_info_handle(entry->srcp_info);
}
if (fdc == pollfds[n].fd) {
srcp_info_handle(entry->srcp_cmd);
}
}
*/
// libwebsocket_service_fd(context, &pollfds[n]);
/* }
}
*/
libwebsocket_context_destroy(lws_context);
magic_close(magic);
return 0;
}

+ 10
- 0
dml_httpd.conf View File

@ -0,0 +1,10 @@
# path for CGI scripts
cgi_path = .
# HTTP root directory
htdocs = ./htdocs/
# HTTP port
port = 8080
# hostname of DML server
dml_host = localhost

+ 257
- 0
htdocs/index.html View File

@ -0,0 +1,257 @@
<html>
<meta charset="UTF-8">
<body bgcolor="white">
<p>
dml_httpd works!
</p>
<div id = "hello">No HELLO from server received.</div>
<div id = "routes"></div>
</body>
<script language="javascript" type="text/javascript">
var DML = {
PACKET: {
HELLO: 0,
ROUTE: 1,
DESCRIPTION: 2,
CERTIFICATE: 3,
HEADER: 4,
CONNECT: 5,
DISC: 6,
REQ_DESCRIPTION: 34,
REQ_CERTIFICATE: 35,
REQ_HEADER: 36,
REQ_REVERSE: 37,
REQ_DISC: 38,
}
};
function char2hex(c)
{
str = c.toString(16);
while (str.length < 2)
str = "0" + str;
return str;
}
function data2str(data)
{
var str = "";
var i;
for (i = 0; i < data.byteLength; i++) {
var c = data.getUint8(i, false);
if (!c)
return str;
str += String.fromCharCode(c);
}
return str;
}
function sha2str(data)
{
if (data instanceof ArrayBuffer)
data = new DataView(data);
var i;
var str = "";
for (i = 0; i < 32; i++) {
str += char2hex(data.getUint8(i));
}
return str;
}
function arraybufcmp(ab1, ab2)
{
if (ab1.byteLength != ab2.byteLength)
return true;
var dv1 = new DataView(ab1);
var dv2 = new DataView(ab2);
var i;
for (i = 0; i < ab1.byteLength; i++) {
if (dv1.getUint8(i) != dv2.getUint8(i))
return true;
}
return false;
}
function dml()
{
url = "ws://" + location.host;
ws = new WebSocket(url);
var dml_this = this;
var packet_hello_cb = function(flags, ident) {}
var packet_route_cb = function(hops, id) {}
var packet_description_cb = function(id, version, bps, mime, name, alias, description) {}
ws.onmessage = function(msg) {
console.log("ws.onmessage: " + msg.data.byteLength);
header = new DataView(msg.data, 0, 4);
id = header.getUint16(0, false);
len = header.getUint16(2, false);
data = new DataView(msg.data, 4);
pos = 4;
console.log("len: " + len + " id: " + id);
switch(id) {
case DML.PACKET.HELLO: {
flags = data.getUint32(0, false);
pos += 4;
data = new DataView(msg.data, pos);
ident = data2str(data);
dml_this.packet_hello_cb(flags, ident);
break;
}
case DML.PACKET.ROUTE: {
hops = data.getUint8(32);
route_id = msg.data.slice(pos, pos + 32);
dml_this.packet_route_cb(hops, route_id);
break;
}
case DML.PACKET.DESCRIPTION: {
desc_id = msg.data.slice(pos, pos + 32);
pos += 32;
data = new DataView(msg.data, pos);
desc_version = data.getUint8(0);
desc_bps = data.getUint32(1, false);
pos += 5;
data = new DataView(msg.data, pos);
desc_mime = data2str(data);
pos += desc_mime.length + 1;
data = new DataView(msg.data, pos);
desc_name = data2str(data);
pos += desc_name.length + 1;
data = new DataView(msg.data, pos);
desc_alias = data2str(data);
pos += desc_alias.length + 1;
data = new DataView(msg.data, pos);
desc_description = data2str(data);
pos += desc_description.length + 1;
dml_this.packet_description_cb(
desc_id, desc_version, desc_bps, desc_mime,
desc_name, desc_alias, desc_description);
break;
}
}
}
ws.onopen = function(event) {
console.log("ws.onopen()");
ws.binaryType = "arraybuffer";
}
ws.onclose = function(event) {
console.log("ws.onclose(): " + event.code + ", " + event.reason);
}
ws.onerror = function(event) {
console.log("ws.onerror(): " + event.data);
}
this.send = function (id, payload_arraybuffer) {
data = new ArrayBuffer(payload_arraybuffer.byteLength + 4);
dataview = new DataView(data);
dataview.setUint16(0, id, false);
dataview.setUint16(2, payload_arraybuffer.byteLength, false);
payloadview = new DataView(payload_arraybuffer);
var i;
for (i = 0; i < payload_arraybuffer.byteLength; i++) {
dataview.setUint8(4 + i, payloadview.getUint8(i));
}
ws.send(data);
}
this.send_req_description = function (id) {
dml_this.send(DML.PACKET.REQ_DESCRIPTION, id);
}
}
var routes = new Array();
function update_routes()
{
var i;
str = "<pre>";
for (i = 0; i < routes.length; i++) {
str += "id: " + sha2str(routes[i].id) + " hops: " + routes[i].hops + "\n";
if (routes[i].description) {
str += "\tbps:\t" + routes[i].description.bps + "\n";
str += "\tmime:\t" + routes[i].description.mime + "\n";
str += "\tname:\t" + routes[i].description.name + "\n";
str += "\talias:\t" + routes[i].description.alias + "\n";
str += "\tdescription:\t" + routes[i].description.description + "\n";
}
}
str += "</pre>";
document.getElementById("routes").innerHTML = str;
}
connection = new dml();
connection.packet_hello_cb = function(flags, ident) {
document.getElementById("hello").innerHTML = "Server identity: " + ident + "<br>Server flags: " + flags;
}
connection.packet_route_cb = function(hops, id) {
var i;
for(i = 0; i < routes.length; i++) {
if (!arraybufcmp(routes[i].id, id))
break;
}
routes[i] = {};
routes[i].id = id;
routes[i].hops = hops;
/* remove route? */
if (hops == 255) {
routes.splice(i, 1);
} else {
/* already got a description? */
if (!routes[i].description)
connection.send_req_description(id);
}
update_routes();
}
connection.packet_description_cb = function(id, version, bps, mime, name, alias, description) {
var i;
for (i = 0; i < routes.length; i++) {
if (!arraybufcmp(routes[i].id, id)) {
routes[i].description = {};
routes[i].description.version = version;
routes[i].description.bps = bps;
routes[i].description.mime = mime;
routes[i].description.name = name;
routes[i].description.alias = alias;
routes[i].description.description = description;
}
}
update_routes();
}
</script>
</html>

Loading…
Cancel
Save