Split HTTP request/response handling -- halfway point to 'blocking' operations.
[pazpar2-moved-to-github.git] / http.c
diff --git a/http.c b/http.c
index f2d6906..93f7707 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,5 +1,5 @@
 /*
- * $Id: http.c,v 1.2 2006-11-24 20:29:07 quinn Exp $
+ * $Id: http.c,v 1.5 2006-12-08 21:40:58 quinn Exp $
  */
 
 #include <stdio.h>
@@ -13,6 +13,7 @@
 #include <fcntl.h>
 #include <netdb.h>
 #include <errno.h>
+#include <assert.h>
 
 #include <yaz/yaz-util.h>
 #include <yaz/comstack.h>
 #include "http_command.h"
 
 static void proxy_io(IOCHAN i, int event);
+static struct http_channel *http_create(void);
+static void http_destroy(IOCHAN i);
 
 extern IOCHAN channel_list;
 
 static struct sockaddr_in *proxy_addr = 0; // If this is set, we proxy normal HTTP requests
 static char proxy_url[256] = "";
 static struct http_buf *http_buf_freelist = 0;
+static struct http_channel *http_channel_freelist = 0;
 
 static struct http_buf *http_buf_create()
 {
@@ -108,6 +112,7 @@ static void http_buf_enqueue(struct http_buf **queue, struct http_buf *b)
 
 static struct http_buf *http_buf_bywrbuf(WRBUF wrbuf)
 {
+    // Heavens to Betsy (buf)!
     return http_buf_bybuf(wrbuf_buf(wrbuf), wrbuf_len(wrbuf));
 }
 
@@ -157,6 +162,28 @@ static int http_buf_read(struct http_buf **b, char *buf, int len)
     return rd;
 }
 
+void static urldecode(char *i, char *o)
+{
+    while (*i)
+    {
+        if (*i == '+')
+        {
+            *(o++) = ' ';
+            i++;
+        }
+        else if (*i == '%')
+        {
+            i++;
+            sscanf(i, "%2hhx", o);
+            i += 2;
+            o++;
+        }
+        else
+            *(o++) = *(i++);
+    }
+    *o = '\0';
+}
+
 void http_addheader(struct http_response *r, const char *name, const char *value)
 {
     struct http_channel *c = r->channel;
@@ -284,6 +311,7 @@ struct http_request *http_parse_request(struct http_channel *c, struct http_buf
             a = nmem_malloc(c->nmem, sizeof(struct http_argument));
             *(equal++) = '\0';
             a->name = nmem_strdup(c->nmem, p2);
+            urldecode(equal, equal);
             a->value = nmem_strdup(c->nmem, equal);
             a->next = r->arguments;
             r->arguments = a;
@@ -348,7 +376,7 @@ static struct http_buf *http_serialize_response(struct http_channel *c,
     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-length: %d\r\n", r->payload ? (int) strlen(r->payload) : 0);
     wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
     wrbuf_puts(c->wrbuf, "\r\n");
 
@@ -389,32 +417,6 @@ static struct http_buf *http_serialize_request(struct http_request *r)
 }
 
 
-// Cleanup
-static void http_destroy(IOCHAN i)
-{
-    struct http_channel *s = iochan_getdata(i);
-
-    yaz_log(YLOG_DEBUG, "Destroying http channel");
-    if (s->proxy)
-    {
-        yaz_log(YLOG_DEBUG, "Destroying Proxy channel");
-        if (s->proxy->iochan)
-        {
-            close(iochan_getfd(s->proxy->iochan));
-            iochan_destroy(s->proxy->iochan);
-        }
-        http_buf_destroy_queue(s->proxy->oqueue);
-        xfree(s->proxy);
-    }
-    http_buf_destroy_queue(s->iqueue);
-    http_buf_destroy_queue(s->oqueue);
-    nmem_destroy(s->nmem);
-    wrbuf_free(s->wrbuf, 1);
-    xfree(s);
-    close(iochan_getfd(i));
-    iochan_destroy(i);
-}
-
 static int http_weshouldproxy(struct http_request *rq)
 {
     if (proxy_addr && !strstr(rq->path, "search.pz2"))
@@ -429,8 +431,6 @@ static int http_proxy(struct http_request *rq)
     struct http_header *hp;
     struct http_buf *requestbuf;
 
-    yaz_log(YLOG_DEBUG, "Proxy request");
-
     if (!p) // This is a new connection. Create a proxy channel
     {
         int sock;
@@ -438,7 +438,6 @@ static int http_proxy(struct http_request *rq)
         int one = 1;
         int flags;
 
-        yaz_log(YLOG_DEBUG, "Creating a new proxy channel");
         if (!(pe = getprotobyname("tcp"))) {
             abort();
         }
@@ -488,11 +487,27 @@ static int http_proxy(struct http_request *rq)
     return 0;
 }
 
+void http_send_response(struct http_channel *ch)
+{
+    struct http_response *rs = ch->response;
+    assert(rs);
+    struct http_buf *hb = http_serialize_response(ch, rs);
+    if (!hb)
+    {
+        yaz_log(YLOG_WARN, "Failed to serialize HTTP response");
+        http_destroy(ch->iochan);
+    }
+    else
+    {
+        http_buf_enqueue(&ch->oqueue, hb);
+        iochan_setflag(ch->iochan, EVENT_OUTPUT);
+        ch->state = Http_Idle;
+    }
+}
+
 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)
     {
@@ -500,13 +515,10 @@ static void http_io(IOCHAN i, int event)
         struct http_buf *htbuf;
 
         case EVENT_INPUT:
-            yaz_log(YLOG_DEBUG, "HTTP Input event");
-
             htbuf = http_buf_create();
             res = read(iochan_getfd(i), htbuf->buf, HTTP_BUF_SIZE -1);
             if (res <= 0 && errno != EAGAIN)
             {
-                yaz_log(YLOG_WARN|YLOG_ERRNO, "HTTP read");
                 http_buf_destroy(htbuf);
                 http_destroy(i);
                 return;
@@ -518,42 +530,29 @@ static void http_io(IOCHAN i, int event)
                 http_buf_enqueue(&hc->iqueue, htbuf);
             }
 
+            if (hc->state == Http_Busy)
+                return;
+
             if ((reqlen = request_check(hc->iqueue)) <= 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)", reqlen);
 
             nmem_reset(hc->nmem);
-            if (!(request = http_parse_request(hc, &hc->iqueue, reqlen)))
+            if (!(hc->request = http_parse_request(hc, &hc->iqueue, reqlen)))
             {
                 yaz_log(YLOG_WARN, "Failed to parse request");
                 http_destroy(i);
                 return;
             }
-            yaz_log(YLOG_LOG, "Request: %s %s v %s", request->method,  request->path,
-                    request->http_version);
-            if (http_weshouldproxy(request))
-                http_proxy(request);
+            hc->response = 0;
+            yaz_log(YLOG_LOG, "Request: %s %s v %s", hc->request->method,
+                    hc->request->path, hc->request->http_version);
+            if (http_weshouldproxy(hc->request))
+                http_proxy(hc->request);
             else
             {
-                struct http_buf *hb;
                 // Execute our business logic!
-                response = http_command(request);
-                if (!response)
-                {
-                    http_destroy(i);
-                    return;
-                }
-                if (!(hb =  http_serialize_response(hc, response)))
-                {
-                    http_destroy(i);
-                    return;
-                }
-                http_buf_enqueue(&hc->oqueue, hb);
-                yaz_log(YLOG_DEBUG, "Response ready");
-                iochan_setflags(i, EVENT_OUTPUT); // Turns off input selecting
+                hc->state = Http_Busy;
+                http_command(hc);
             }
             if (hc->iqueue)
             {
@@ -564,7 +563,6 @@ static void http_io(IOCHAN i, int event)
             break;
 
         case EVENT_OUTPUT:
-            yaz_log(YLOG_DEBUG, "HTTP output event");
             if (hc->oqueue)
             {
                 struct http_buf *wb = hc->oqueue;
@@ -575,7 +573,6 @@ static void http_io(IOCHAN i, int event)
                     http_destroy(i);
                     return;
                 }
-                yaz_log(YLOG_DEBUG, "HTTP Wrote %d octets", res);
                 if (res == wb->len)
                 {
                     hc->oqueue = hc->oqueue->next;
@@ -587,15 +584,17 @@ static void http_io(IOCHAN i, int event)
                     wb->offset += res;
                 }
                 if (!hc->oqueue) {
-                    yaz_log(YLOG_DEBUG, "Writing finished");
                     if (!strcmp(hc->version, "1.0"))
                     {
-                        yaz_log(YLOG_DEBUG, "Closing 1.0 connection");
                         http_destroy(i);
                         return;
                     }
                     else
-                        iochan_setflags(i, EVENT_INPUT); // Turns off output flag
+                    {
+                        iochan_clearflag(i, EVENT_OUTPUT);
+                        if (hc->iqueue)
+                            iochan_setevent(hc->iochan, EVENT_INPUT);
+                    }
                 }
             }
 
@@ -620,10 +619,8 @@ static void proxy_io(IOCHAN pi, int event)
         struct http_buf *htbuf;
 
         case EVENT_INPUT:
-            yaz_log(YLOG_DEBUG, "Proxy input event");
             htbuf = http_buf_create();
             res = read(iochan_getfd(pi), htbuf->buf, HTTP_BUF_SIZE -1);
-            yaz_log(YLOG_DEBUG, "Proxy read %d bytes.", res);
             if (res == 0 || (res < 0 && errno != EINPROGRESS))
             {
                 if (hc->oqueue)
@@ -650,7 +647,6 @@ static void proxy_io(IOCHAN pi, int event)
             iochan_setflag(hc->iochan, EVENT_OUTPUT);
             break;
         case EVENT_OUTPUT:
-            yaz_log(YLOG_DEBUG, "Proxy output event");
             if (!(htbuf = pc->oqueue))
             {
                 iochan_clearflag(pi, EVENT_OUTPUT);
@@ -685,6 +681,53 @@ static void proxy_io(IOCHAN pi, int event)
     }
 }
 
+// Cleanup channel
+static void http_destroy(IOCHAN i)
+{
+    struct http_channel *s = iochan_getdata(i);
+
+    if (s->proxy)
+    {
+        if (s->proxy->iochan)
+        {
+            close(iochan_getfd(s->proxy->iochan));
+            iochan_destroy(s->proxy->iochan);
+        }
+        http_buf_destroy_queue(s->proxy->oqueue);
+        xfree(s->proxy);
+    }
+    s->next = http_channel_freelist;
+    http_channel_freelist = s;
+    close(iochan_getfd(i));
+    iochan_destroy(i);
+}
+
+static struct http_channel *http_create(void)
+{
+    struct http_channel *r = http_channel_freelist;
+
+    if (r)
+    {
+        http_channel_freelist = r->next;
+        nmem_reset(r->nmem);
+        wrbuf_rewind(r->wrbuf);
+    }
+    else
+    {
+        r = xmalloc(sizeof(struct http_channel));
+        r->nmem = nmem_create();
+        r->wrbuf = wrbuf_alloc();
+    }
+    r->proxy = 0;
+    r->iochan = 0;
+    r->iqueue = r->oqueue = 0;
+    r->state = Http_Idle;
+    r->request = 0;
+    r->response = 0;
+    return r;
+}
+
+
 /* Accept a new command connection */
 static void http_accept(IOCHAN i, int event)
 {
@@ -710,12 +753,8 @@ static void http_accept(IOCHAN i, int event)
     yaz_log(YLOG_LOG, "New command connection");
     c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
 
-    ch = xmalloc(sizeof(*ch));
-    ch->proxy = 0;
-    ch->nmem = nmem_create();
-    ch->wrbuf = wrbuf_alloc();
+    ch = http_create();
     ch->iochan = c;
-    ch->iqueue = ch->oqueue = 0;
     iochan_setdata(c, ch);
 
     c->next = channel_list;