From 3dbff6d94b35be949f03bcd922129458747bcda2 Mon Sep 17 00:00:00 2001 From: Sebastian Hammer Date: Tue, 21 Nov 2006 18:46:43 +0000 Subject: [PATCH] Added basic HTTP server logic --- Makefile | 4 +- http.c | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ http.h | 60 ++++++++++ http_command.c | 121 +++++++++++++++++++ http_command.h | 8 ++ pazpar2.c | 13 ++- pazpar2.h | 1 + 7 files changed, 560 insertions(+), 4 deletions(-) create mode 100644 http.c create mode 100644 http.h create mode 100644 http_command.c create mode 100644 http_command.h diff --git a/Makefile b/Makefile index 51ce3df..8e2b9a5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # ParaZ. Copyright (C) 2000-2004, Index Data ApS # All rights reserved. -# $Id: Makefile,v 1.1 2006-11-14 20:44:38 quinn Exp $ +# $Id: Makefile,v 1.2 2006-11-21 18:46:43 quinn Exp $ SHELL=/bin/sh @@ -11,7 +11,7 @@ YAZLIBS=`$(YAZCONF) --libs` YAZCFLAGS=`$(YAZCONF) --cflags` PROG=pazpar2 -PROGO=pazpar2.o eventl.o util.o command.o +PROGO=pazpar2.o eventl.o util.o command.o http.o http_command.o all: $(PROG) diff --git a/http.c b/http.c new file mode 100644 index 0000000..9de5c05 --- /dev/null +++ b/http.c @@ -0,0 +1,357 @@ +/* + * $Id: http.c,v 1.1 2006-11-21 18:46:43 quinn Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "command.h" +#include "util.h" +#include "eventl.h" +#include "pazpar2.h" +#include "http.h" +#include "http_command.h" + +extern IOCHAN channel_list; + +void http_addheader(struct http_response *r, const char *name, const char *value) +{ + struct http_channel *c = r->channel; + struct http_header *h = nmem_malloc(c->nmem, sizeof *h); + h->name = nmem_strdup(c->nmem, name); + h->value = nmem_strdup(c->nmem, value); + h->next = r->headers; + r->headers = h; +} + +char *argbyname(struct http_request *r, char *name) +{ + struct http_argument *p; + for (p = r->arguments; p; p = p->next) + if (!strcmp(p->name, name)) + return p->value; + return 0; +} + +char *headerbyname(struct http_request *r, char *name) +{ + struct http_header *p; + for (p = r->headers; p; p = p->next) + if (!strcmp(p->name, name)) + return p->value; + return 0; +} + +struct http_response *http_create_response(struct http_channel *c) +{ + struct http_response *r = nmem_malloc(c->nmem, sizeof(*r)); + strcpy(r->code, "200"); + r->msg = "OK"; + r->channel = c; + r->headers = 0; + r->payload = 0; + return r; +} + +// Check if we have a complete request. Return 0 or length (including trailing newline) +// FIXME: Does not deal gracefully with requests carrying payload +// but this is kind of OK since we will reject anything other than an empty GET +static int request_check(const char *buf) +{ + int len = 0; + + while (*buf) // Check if we have a sequence of lines terminated by an empty line + { + char *b = strstr(buf, "\r\n"); + + if (!b) + return 0; + + len += (b - buf) + 2; + if (b == buf) + return len; + buf = b + 2; + } + return 0; +} + +struct http_request *http_parse_request(struct http_channel *c, char *buf) +{ + struct http_request *r = nmem_malloc(c->nmem, sizeof(*r)); + char *p, *p2; + + r->channel = c; + // Parse first line + if (!strncmp(buf, "GET ", 4)) + r->method = Method_GET; + else + { + yaz_log(YLOG_WARN, "Unexpected HTTP method in request"); + return 0; + } + if (!(buf = strchr(buf, ' '))) + { + yaz_log(YLOG_WARN, "Syntax error in request (1)"); + return 0; + } + buf++; + if (!(p = strchr(buf, ' '))) + { + yaz_log(YLOG_WARN, "Syntax error in request (2)"); + return 0; + } + *(p++) = '\0'; + if ((p2 = strchr(buf, '?'))) // Do we have arguments? + *(p2++) = '\0'; + r->path = nmem_strdup(c->nmem, buf); + if (p2) + { + // Parse Arguments + while (*p2) + { + struct http_argument *a; + char *equal = strchr(p2, '='); + char *eoa = strchr(p2, '&'); + if (!equal) + { + yaz_log(YLOG_WARN, "Expected '=' in argument"); + return 0; + } + if (!eoa) + eoa = equal + strlen(equal); // last argument + else + *(eoa++) = '\0'; + a = nmem_malloc(c->nmem, sizeof(struct http_argument)); + *(equal++) = '\0'; + a->name = nmem_strdup(c->nmem, p2); + a->value = nmem_strdup(c->nmem, equal); + a->next = r->arguments; + r->arguments = a; + p2 = eoa; + } + } + buf = p; + + if (strncmp(buf, "HTTP/", 5)) + strcpy(r->http_version, "1.0"); + else + { + buf += 5; + if (!(p = strstr(buf, "\r\n"))) + return 0; + *(p++) = '\0'; + strcpy(r->http_version, buf); + buf = p; + } + strcpy(c->version, r->http_version); + + r->headers = 0; // We might want to parse these someday + + return r; +} + + +static char *http_serialize_response(struct http_channel *c, struct http_response *r) +{ + wrbuf_rewind(c->wrbuf); + struct http_header *h; + + wrbuf_printf(c->wrbuf, "HTTP/1.1 %s %s\r\n", r->code, r->msg); + for (h = r->headers; h; h = h->next) + wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value); + wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ? strlen(r->payload) : 0); + wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n"); + wrbuf_puts(c->wrbuf, "\r\n"); + + if (r->payload) + wrbuf_puts(c->wrbuf, r->payload); + + wrbuf_putc(c->wrbuf, '\0'); + return wrbuf_buf(c->wrbuf); +} + +// Cleanup +static void http_destroy(IOCHAN i) +{ + struct http_channel *s = iochan_getdata(i); + + yaz_log(YLOG_DEBUG, "Destroying http channel"); + nmem_destroy(s->nmem); + wrbuf_free(s->wrbuf, 1); + xfree(s); + close(iochan_getfd(i)); + iochan_destroy(i); +} + +static void http_io(IOCHAN i, int event) +{ + struct http_channel *hc = iochan_getdata(i); + struct http_request *request; + struct http_response *response; + + switch (event) + { + int res; + + case EVENT_INPUT: + yaz_log(YLOG_DEBUG, "HTTP Input event"); + + res = read(iochan_getfd(i), hc->ibuf + hc->read, IBUF_SIZE - (hc->read + 1)); + if (res <= 0) + { + yaz_log(YLOG_WARN|YLOG_ERRNO, "HTTP read"); + http_destroy(i); + return; + } + yaz_log(YLOG_DEBUG, "HTTP read %d octets", res); + hc->read += res; + hc->ibuf[hc->read] = '\0'; + + if ((res = request_check(hc->ibuf)) <= 2) + { + yaz_log(YLOG_DEBUG, "We don't have a complete HTTP request yet"); + return; + } + yaz_log(YLOG_DEBUG, "We think we have a complete HTTP request (len %d): \n%s", res, hc->ibuf); + nmem_reset(hc->nmem); + if (!(request = http_parse_request(hc, hc->ibuf))) + { + yaz_log(YLOG_WARN, "Failed to parse request"); + http_destroy(i); + return; + } + response = http_command(request); + if (!response) + { + http_destroy(i); + return; + } + // FIXME -- do something to cause the response to be sent to the client + if (!(hc->obuf = http_serialize_response(hc, response))) + { + http_destroy(i); + return; + } + yaz_log(YLOG_DEBUG, "Response ready:\n%s", hc->obuf); + hc->writ = 0; + hc->read = 0; + iochan_setflags(i, EVENT_OUTPUT); // Turns off input selecting + break; + + case EVENT_OUTPUT: + yaz_log(YLOG_DEBUG, "HTTP output event"); + res = write(iochan_getfd(hc->iochan), hc->obuf + hc->writ, + strlen(hc->obuf + hc->writ)); + if (res <= 0) + { + yaz_log(YLOG_WARN|YLOG_ERRNO, "write"); + http_destroy(i); + return; + } + hc->writ += res; + if (!hc->obuf[hc->writ]) { + yaz_log(YLOG_DEBUG, "Writing finished"); + if (!strcmp(hc->version, "1.0")) + { + yaz_log(YLOG_DEBUG, "Closing 1.0 connection"); + http_destroy(i); + } + else + iochan_setflags(i, EVENT_INPUT); // Turns off output flag + } + break; + default: + yaz_log(YLOG_WARN, "Unexpected event on connection"); + http_destroy(i); + } +} + +/* Accept a new command connection */ +static void http_accept(IOCHAN i, int event) +{ + struct sockaddr_in addr; + int fd = iochan_getfd(i); + socklen_t len; + int s; + IOCHAN c; + int flags; + struct http_channel *ch; + + len = sizeof addr; + if ((s = accept(fd, (struct sockaddr *) &addr, &len)) < 0) + { + yaz_log(YLOG_WARN|YLOG_ERRNO, "accept"); + return; + } + if ((flags = fcntl(s, F_GETFL, 0)) < 0) + yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl"); + if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) + yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2"); + + yaz_log(YLOG_LOG, "New command connection"); + c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT); + + ch = xmalloc(sizeof(*ch)); + ch->read = 0; + ch->nmem = nmem_create(); + ch->wrbuf = wrbuf_alloc(); + ch->iochan = c; + iochan_setdata(c, ch); + + c->next = channel_list; + channel_list = c; +} + + +/* Create a http-channel listener */ +void http_init(int port) +{ + IOCHAN c; + int l; + struct protoent *p; + struct sockaddr_in myaddr; + int one = 1; + + yaz_log(YLOG_LOG, "HTTP port is %d", port); + if (!(p = getprotobyname("tcp"))) { + abort(); + } + if ((l = socket(PF_INET, SOCK_STREAM, p->p_proto)) < 0) + yaz_log(YLOG_FATAL|YLOG_ERRNO, "socket"); + if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, (char*) + &one, sizeof(one)) < 0) + abort(); + + bzero(&myaddr, sizeof myaddr); + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = INADDR_ANY; + myaddr.sin_port = htons(port); + if (bind(l, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) + yaz_log(YLOG_FATAL|YLOG_ERRNO, "bind"); + if (listen(l, SOMAXCONN) < 0) + yaz_log(YLOG_FATAL|YLOG_ERRNO, "listen"); + + c = iochan_create(l, http_accept, EVENT_INPUT | EVENT_EXCEPT); + c->next = channel_list; + channel_list = c; +} + + +/* + * Local variables: + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + * vim: shiftwidth=4 tabstop=8 expandtab + */ diff --git a/http.h b/http.h new file mode 100644 index 0000000..bb15ac7 --- /dev/null +++ b/http.h @@ -0,0 +1,60 @@ +#ifndef HTTP_H +#define HTTP_H + +struct http_channel +{ + IOCHAN iochan; +#define IBUF_SIZE 10240 + char ibuf[IBUF_SIZE]; + char version[10]; + int read; + char *obuf; + int writ; + NMEM nmem; + WRBUF wrbuf; +}; + +struct http_header +{ + char *name; + char *value; + struct http_header *next; +}; + +struct http_argument +{ + char *name; + char *value; + struct http_argument *next; +}; + +struct http_request +{ + struct http_channel *channel; + char http_version[20]; + enum + { + Method_GET, + Method_other + } method; + char *path; + struct http_header *headers; + struct http_argument *arguments; +}; + +struct http_response +{ + char code[4]; + char *msg; + struct http_channel *channel; + struct http_header *headers; + char *payload; +}; + +void http_init(int port); +void http_addheader(struct http_response *r, const char *name, const char *value); +char *argbyname(struct http_request *r, char *name); +char *headerbyname(struct http_request *r, char *name); +struct http_response *http_create_response(struct http_channel *c); + +#endif diff --git a/http_command.c b/http_command.c new file mode 100644 index 0000000..07210ec --- /dev/null +++ b/http_command.c @@ -0,0 +1,121 @@ +/* + * $Id: http_command.c,v 1.1 2006-11-21 18:46:43 quinn Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "command.h" +#include "util.h" +#include "eventl.h" +#include "pazpar2.h" +#include "http.h" +#include "http_command.h" + +struct http_session { + struct session *psession; + char session_id[128]; + int timestamp; + struct http_session *next; +}; + +static struct http_session *session_list = 0; + +struct http_session *http_session_create() +{ + struct http_session *r = xmalloc(sizeof(*r)); + r->psession = 0; + *r->session_id = '\0'; + r->timestamp = 0; + r->next = session_list; + session_list = r; + return r; +} + +void http_session_destroy(struct http_session *s) +{ + struct http_session **p; + + for (p = &session_list; *p; p = &(*p)->next) + if (*p == s) + { + *p = (*p)->next; + break; + } + session_destroy(s->psession); + xfree(s); +} + +static void error(struct http_response *rs, char *code, char *msg, char *txt) +{ + struct http_channel *c = rs->channel; + char tmp[1024]; + + if (!txt) + txt = msg; + rs->msg = nmem_strdup(c->nmem, msg); + strcpy(rs->code, code); + sprintf(tmp, "%s", txt); + rs->payload = nmem_strdup(c->nmem, tmp); +} + +static void cmd_init(struct http_request *rq, struct http_response *rs) +{ +} + +static void cmd_stat(struct http_request *rq, struct http_response *rs) +{ +} + +static void cmd_load(struct http_request *rq, struct http_response *rs) +{ +} + +struct { + char *name; + void (*fun)(struct http_request *rq, struct http_response *rs); +} commands[] = { + { "init", cmd_init }, + { "stat", cmd_stat }, + { "load", cmd_load }, + {0,0} +}; + +struct http_response *http_command(struct http_request *rq) +{ + char *command = argbyname(rq, "command"); + struct http_channel *c = rq->channel; + struct http_response *rs = http_create_response(c); + int i; + + if (!command) + { + error(rs, "417", "Must supply command", 0); + return rs; + } + for (i = 0; commands[i].name; i++) + if (!strcmp(commands[i].name, command)) + { + (*commands[i].fun)(rq, rs); + break; + } + if (!commands[i].name) + error(rs, "417", "Unknown command", 0); + + return rs; +} + +/* + * Local variables: + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + * vim: shiftwidth=4 tabstop=8 expandtab + */ diff --git a/http_command.h b/http_command.h new file mode 100644 index 0000000..67f9adb --- /dev/null +++ b/http_command.h @@ -0,0 +1,8 @@ +#ifndef HTTP_COMMAND_H +#define HTTP_COMMAND + +#include "http.h" + +struct http_response *http_command(struct http_request *r); + +#endif diff --git a/pazpar2.c b/pazpar2.c index bcedea6..b4ca296 100644 --- a/pazpar2.c +++ b/pazpar2.c @@ -1,4 +1,4 @@ -/* $Id: pazpar2.c,v 1.2 2006-11-18 05:00:38 quinn Exp $ */ +/* $Id: pazpar2.c,v 1.3 2006-11-21 18:46:43 quinn Exp $ */ #include #include @@ -20,6 +20,7 @@ #include "pazpar2.h" #include "eventl.h" #include "command.h" +#include "http.h" #define PAZPAR2_VERSION "0.1" #define MAX_DATABASES 512 @@ -847,6 +848,11 @@ struct session *new_session() return session; } +void session_destroy(struct session *s) +{ + // FIXME do some shit here!!!! +} + struct hitsbytarget *hitsbytarget(struct session *s, int *count) { static struct hitsbytarget res[1000]; // FIXME MM @@ -930,7 +936,7 @@ int main(int argc, char **argv) yaz_log_init(YLOG_DEFAULT_LEVEL|YLOG_DEBUG, "pazpar2", 0); - while ((ret = options("c:", argv, argc, &arg)) != -2) + while ((ret = options("c:h:", argv, argc, &arg)) != -2) { switch (ret) { case 0: @@ -938,6 +944,9 @@ int main(int argc, char **argv) case 'c': command_init(atoi(arg)); break; + case 'h': + http_init(atoi(arg)); + break; default: fprintf(stderr, "Usage: pazpar2 -d comport"); exit(1); diff --git a/pazpar2.h b/pazpar2.h index 667dab0..0a737ac 100644 --- a/pazpar2.h +++ b/pazpar2.h @@ -47,6 +47,7 @@ struct hitsbytarget { struct hitsbytarget *hitsbytarget(struct session *s, int *count); struct session *new_session(); +void session_destroy(struct session *s); int load_targets(struct session *s, const char *fn); void statistics(struct session *s, struct statistics *stat); void search(struct session *s, char *query); -- 1.7.10.4