]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/protocol-http.c
Merge remote branch 'mkbosmans/rate-adjustment'
[pulseaudio] / src / pulsecore / protocol-http.c
index 08a70e50e0c63e057496fed821b485033035dd8c..83067f8548e3abf79407c742b60778d24b684c16 100644 (file)
@@ -1,7 +1,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2005-2006 Lennart Poettering
+  Copyright 2005-2009 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <errno.h>
 
 #include <pulse/util.h>
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/ioline.h>
+#include <pulsecore/thread-mq.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/log.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/cli-text.h>
 #include <pulsecore/shared.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/mime-type.h>
 
 #include "protocol-http.h"
 
@@ -46,7 +51,7 @@
 #define URL_CSS "/style"
 #define URL_STATUS "/status"
 #define URL_LISTEN "/listen"
-#define URL_LISTEN_PREFIX "/listen/"
+#define URL_LISTEN_SOURCE "/listen/source/"
 
 #define MIME_HTML "text/html; charset=utf-8"
 #define MIME_TEXT "text/plain; charset=utf-8"
 #define HTML_FOOTER                                                     \
     "        </body>\n"                                                 \
     "</html>\n"
+
+#define RECORD_BUFFER_SECONDS (5)
+#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
+
 enum state {
     STATE_REQUEST_LINE,
     STATE_MIME_HEADER,
     STATE_DATA
 };
 
+enum method {
+    METHOD_GET,
+    METHOD_HEAD
+};
+
 struct connection {
     pa_http_protocol *protocol;
+    pa_iochannel *io;
     pa_ioline *line;
+    pa_memblockq *output_memblockq;
+    pa_source_output *source_output;
+    pa_client *client;
     enum state state;
     char *url;
+    enum method method;
     pa_module *module;
 };
 
@@ -84,157 +103,169 @@ struct pa_http_protocol {
 
     pa_core *core;
     pa_idxset *connections;
+
+    pa_strlist *servers;
 };
 
+enum {
+    SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
+};
 
-static pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) {
+/* Called from main context */
+static void connection_unlink(struct connection *c) {
+    pa_assert(c);
 
-    pa_assert(pa_channel_map_compatible(cm, ss));
+    if (c->source_output) {
+        pa_source_output_unlink(c->source_output);
+        c->source_output->userdata = NULL;
+        pa_source_output_unref(c->source_output);
+    }
 
-    switch (ss->format) {
-        case PA_SAMPLE_S16BE:
-        case PA_SAMPLE_S24BE:
-        case PA_SAMPLE_U8:
+    if (c->client)
+        pa_client_free(c->client);
 
-            if (ss->rate != 8000 &&
-                ss->rate != 11025 &&
-                ss->rate != 16000 &&
-                ss->rate != 22050 &&
-                ss->rate != 24000 &&
-                ss->rate != 32000 &&
-                ss->rate != 44100 &&
-                ss->rate != 48000)
-                return FALSE;
+    pa_xfree(c->url);
 
-            if (ss->channels != 1 &&
-                ss->channels != 2)
-                return FALSE;
+    if (c->line)
+        pa_ioline_unref(c->line);
 
-            if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) ||
-                (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT)))
-                return FALSE;
+    if (c->io)
+        pa_iochannel_free(c->io);
 
-            return TRUE;
+    if (c->output_memblockq)
+        pa_memblockq_free(c->output_memblockq);
 
-        case PA_SAMPLE_ULAW:
+    pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
 
-            if (ss->rate != 8000)
-                return FALSE;
+    pa_xfree(c);
+}
+
+/* Called from main context */
+static int do_write(struct connection *c) {
+    pa_memchunk chunk;
+    ssize_t r;
+    void *p;
 
-            if (ss->channels != 1)
-                return FALSE;
+    pa_assert(c);
 
-            if (cm->map[0] != PA_CHANNEL_POSITION_MONO)
-                return FALSE;
+    if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
+        return 0;
 
-            return TRUE;
+    pa_assert(chunk.memblock);
+    pa_assert(chunk.length > 0);
 
-        default:
-            return FALSE;
+    p = pa_memblock_acquire(chunk.memblock);
+    r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
+    pa_memblock_release(chunk.memblock);
+
+    pa_memblock_unref(chunk.memblock);
+
+    if (r < 0) {
+
+        if (errno == EINTR || errno == EAGAIN)
+            return 0;
+
+        pa_log("write(): %s", pa_cstrerror(errno));
+        return -1;
     }
+
+    pa_memblockq_drop(c->output_memblockq, (size_t) r);
+
+    return 0;
 }
 
-static void mimefy_sample_spec(pa_sample_spec *ss, pa_channel_map *cm) {
-
-    pa_assert(pa_channel_map_compatible(cm, ss));
-
-    /* Turns the sample type passed in into the next 'better' one that
-     * can be encoded for HTTP. If there is no 'better' one we pick
-     * the 'best' one that is 'worse'. */
-
-    if (ss->channels > 2)
-        ss->channels = 2;
-
-    if (ss->rate > 44100)
-        ss->rate = 48000;
-    else if (ss->rate > 32000)
-        ss->rate = 44100;
-    else if (ss->rate > 24000)
-        ss->rate = 32000;
-    else if (ss->rate > 22050)
-        ss->rate = 24000;
-    else if (ss->rate > 16000)
-        ss->rate = 22050;
-    else if (ss->rate > 11025)
-        ss->rate = 16000;
-    else if (ss->rate > 8000)
-        ss->rate = 11025;
-    else
-        ss->rate = 8000;
-
-    switch (ss->format) {
-        case PA_SAMPLE_S24BE:
-        case PA_SAMPLE_S24LE:
-        case PA_SAMPLE_S24_32LE:
-        case PA_SAMPLE_S24_32BE:
-        case PA_SAMPLE_S32LE:
-        case PA_SAMPLE_S32BE:
-        case PA_SAMPLE_FLOAT32LE:
-        case PA_SAMPLE_FLOAT32BE:
-            ss->format = PA_SAMPLE_S24BE;
-            break;
+/* Called from main context */
+static void do_work(struct connection *c) {
+    pa_assert(c);
 
-        case PA_SAMPLE_S16BE:
-        case PA_SAMPLE_S16LE:
-            ss->format = PA_SAMPLE_S16BE;
-            break;
+    if (pa_iochannel_is_hungup(c->io))
+        goto fail;
 
-        case PA_SAMPLE_ULAW:
-        case PA_SAMPLE_ALAW:
+    if (pa_iochannel_is_writable(c->io))
+        if (do_write(c) < 0)
+            goto fail;
 
-            if (ss->rate == 8000 && ss->channels == 1)
-                ss->format = PA_SAMPLE_ULAW;
-            else
-                ss->format = PA_SAMPLE_S16BE;
-            break;
+    return;
+
+fail:
+    connection_unlink(c);
+}
+
+/* Called from thread context, except when it is not */
+static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+    pa_source_output *o = PA_SOURCE_OUTPUT(m);
+    struct connection *c;
 
-        case PA_SAMPLE_U8:
-            ss->format = PA_SAMPLE_U8;
+    pa_source_output_assert_ref(o);
+
+    if (!(c = o->userdata))
+        return -1;
+
+    switch (code) {
+
+        case SOURCE_OUTPUT_MESSAGE_POST_DATA:
+            /* While this function is usually called from IO thread
+             * context, this specific command is not! */
+            pa_memblockq_push_align(c->output_memblockq, chunk);
+            do_work(c);
             break;
 
-        case PA_SAMPLE_MAX:
-        case PA_SAMPLE_INVALID:
-            pa_assert_not_reached();
+        default:
+            return pa_source_output_process_msg(m, code, userdata, offset, chunk);
     }
 
-    pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT);
+    return 0;
+}
 
-    pa_assert(is_mime_sample_spec(ss, cm));
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+    struct connection *c;
+
+    pa_source_output_assert_ref(o);
+    pa_assert_se(c = o->userdata);
+    pa_assert(chunk);
+
+    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
 }
 
-static char *sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) {
-    pa_assert(pa_channel_map_compatible(cm, ss));
+/* Called from main context */
+static void source_output_kill_cb(pa_source_output *o) {
+    struct connection*c;
 
-    if (!is_mime_sample_spec(ss, cm))
-        return NULL;
+    pa_source_output_assert_ref(o);
+    pa_assert_se(c = o->userdata);
 
-    switch (ss->format) {
+    connection_unlink(c);
+}
 
-        case PA_SAMPLE_S16BE:
-        case PA_SAMPLE_S24BE:
-        case PA_SAMPLE_U8:
-            return pa_sprintf_malloc("audio/%s; rate=%u; channels=%u",
-                                     ss->format == PA_SAMPLE_S16BE ? "L16" :
-                                     (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"),
-                                     ss->rate, ss->channels);
+/* Called from main context */
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+    struct connection*c;
 
-        case PA_SAMPLE_ULAW:
-            return pa_xstrdup("audio/basic");
+    pa_source_output_assert_ref(o);
+    pa_assert_se(c = o->userdata);
 
-        default:
-            pa_assert_not_reached();
-    }
+    return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
+}
+
+/*** client callbacks ***/
+static void client_kill_cb(pa_client *client) {
+    struct connection*c;
+
+    pa_assert(client);
+    pa_assert_se(c = client->userdata);
 
-    pa_assert(pa_sample_spec_valid(ss));
+    connection_unlink(c);
 }
 
-static char *mimefy_and_stringify_sample_spec(const pa_sample_spec *_ss, const pa_channel_map *_cm) {
-    pa_sample_spec ss = *_ss;
-    pa_channel_map cm = *_cm;
+/*** pa_iochannel callbacks ***/
+static void io_callback(pa_iochannel*io, void *userdata) {
+    struct connection *c = userdata;
 
-    mimefy_sample_spec(&ss, &cm);
+    pa_assert(c);
+    pa_assert(io);
 
-    return sample_spec_to_mime_type(&ss, &cm);
+    do_work(c);
 }
 
 static char *escape_html(const char *t) {
@@ -302,6 +333,11 @@ static void html_response(
 
     http_response(c, code, msg, MIME_HTML);
 
+    if (c->method == METHOD_HEAD) {
+        pa_ioline_defer_close(c->line);
+        return;
+    }
+
     if (!text)
         text = msg;
 
@@ -317,26 +353,6 @@ static void html_response(
     pa_ioline_defer_close(c->line);
 }
 
-static void internal_server_error(struct connection *c) {
-    pa_assert(c);
-
-    html_response(c, 500, "Internal Server Error", NULL);
-}
-
-static void connection_unlink(struct connection *c) {
-    pa_assert(c);
-
-    if (c->url)
-        pa_xfree(c->url);
-
-    if (c->line)
-        pa_ioline_unref(c->line);
-
-    pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
-
-    pa_xfree(c);
-}
-
 static void html_print_field(pa_ioline *line, const char *left, const char *right) {
     char *eleft, *eright;
 
@@ -351,128 +367,265 @@ static void html_print_field(pa_ioline *line, const char *left, const char *righ
     pa_xfree(eright);
 }
 
-static void handle_url(struct connection *c) {
+static void handle_root(struct connection *c) {
+    char *t;
+
     pa_assert(c);
 
-    pa_log_debug("Request for %s", c->url);
+    http_response(c, 200, "OK", MIME_HTML);
 
-    if (pa_streq(c->url, URL_ROOT)) {
-        char *t;
+    if (c->method == METHOD_HEAD) {
+        pa_ioline_defer_close(c->line);
+        return;
+    }
 
-        http_response(c, 200, "OK", MIME_HTML);
+    pa_ioline_puts(c->line,
+                   HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
+                   "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
+                   "<table>\n");
 
-        pa_ioline_puts(c->line,
-                       HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
-                       "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
-                       "<table>\n");
+    t = pa_get_user_name_malloc();
+    html_print_field(c->line, "User Name:", t);
+    pa_xfree(t);
 
-        t = pa_get_user_name_malloc();
-        html_print_field(c->line, "User Name:", t);
-        pa_xfree(t);
+    t = pa_get_host_name_malloc();
+    html_print_field(c->line, "Host name:", t);
+    pa_xfree(t);
 
-        t = pa_get_host_name_malloc();
-        html_print_field(c->line, "Host name:", t);
-        pa_xfree(t);
+    t = pa_machine_id();
+    html_print_field(c->line, "Machine ID:", t);
+    pa_xfree(t);
 
-        t = pa_machine_id();
-        html_print_field(c->line, "Machine ID:", t);
-        pa_xfree(t);
+    t = pa_uname_string();
+    html_print_field(c->line, "System:", t);
+    pa_xfree(t);
 
-        t = pa_uname_string();
-        html_print_field(c->line, "System:", t);
-        pa_xfree(t);
+    t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
+    html_print_field(c->line, "Process ID:", t);
+    pa_xfree(t);
 
-        t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
-        html_print_field(c->line, "Process ID:", t);
-        pa_xfree(t);
+    pa_ioline_puts(c->line,
+                   "</table>\n"
+                   "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
+                   "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
+                   HTML_FOOTER);
 
-        pa_ioline_puts(c->line,
-                       "</table>\n"
-                       "<p><a href=\"/status\">Show an extensive server status report</a></p>\n"
-                       "<p><a href=\"/listen\">Monitor sinks and sources</a></p>\n"
-                       HTML_FOOTER);
+    pa_ioline_defer_close(c->line);
+}
 
+static void handle_css(struct connection *c) {
+    pa_assert(c);
+
+    http_response(c, 200, "OK", MIME_CSS);
+
+    if (c->method == METHOD_HEAD) {
         pa_ioline_defer_close(c->line);
+        return;
+    }
 
-    } else if (pa_streq(c->url, URL_CSS)) {
-        http_response(c, 200, "OK", MIME_CSS);
+    pa_ioline_puts(c->line,
+                   "body { color: black; background-color: white; }\n"
+                   "a:link, a:visited { color: #900000; }\n"
+                   "div.news-date { font-size: 80%; font-style: italic; }\n"
+                   "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
+                   ".grey { color: #8f8f8f; font-size: 80%; }"
+                   "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
+                   "td { padding-left:10px; padding-right:10px; }\n");
 
-        pa_ioline_puts(c->line,
-                       "body { color: black; background-color: white; }\n"
-                       "a:link, a:visited { color: #900000; }\n"
-                       "div.news-date { font-size: 80%; font-style: italic; }\n"
-                       "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
-                       ".grey { color: #8f8f8f; font-size: 80%; }"
-                       "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
-                       "td { padding-left:10px; padding-right:10px; }\n");
+    pa_ioline_defer_close(c->line);
+}
+
+static void handle_status(struct connection *c) {
+    char *r;
+
+    pa_assert(c);
+
+    http_response(c, 200, "OK", MIME_TEXT);
 
+    if (c->method == METHOD_HEAD) {
         pa_ioline_defer_close(c->line);
+        return;
+    }
+
+    r = pa_full_status_string(c->protocol->core);
+    pa_ioline_puts(c->line, r);
+    pa_xfree(r);
 
-    } else if (pa_streq(c->url, URL_STATUS)) {
-        char *r;
+    pa_ioline_defer_close(c->line);
+}
 
-        http_response(c, 200, "OK", MIME_TEXT);
-        r = pa_full_status_string(c->protocol->core);
-        pa_ioline_puts(c->line, r);
-        pa_xfree(r);
+static void handle_listen(struct connection *c) {
+    pa_source *source;
+    pa_sink *sink;
+    uint32_t idx;
 
+    http_response(c, 200, "OK", MIME_HTML);
+
+    pa_ioline_puts(c->line,
+                   HTML_HEADER("Listen")
+                   "<h2>Sinks</h2>\n"
+                   "<p>\n");
+
+    if (c->method == METHOD_HEAD) {
         pa_ioline_defer_close(c->line);
+        return;
+    }
 
-    } else if (pa_streq(c->url, URL_LISTEN)) {
-        pa_source *source;
-        pa_sink *sink;
-        uint32_t idx;
+    PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
+        char *t, *m;
 
-        http_response(c, 200, "OK", MIME_HTML);
+        t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+        m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
 
-        pa_ioline_puts(c->line,
-                       HTML_HEADER("Listen")
-                       "<h2>Sinks</h2>\n"
-                       "<p>\n");
+        pa_ioline_printf(c->line,
+                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
+                         sink->monitor_source->name, m, t);
 
-        PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
-            char *t, *m;
+        pa_xfree(t);
+        pa_xfree(m);
+    }
 
-            t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
-            m = mimefy_and_stringify_sample_spec(&sink->sample_spec, &sink->channel_map);
+    pa_ioline_puts(c->line,
+                   "</p>\n"
+                   "<h2>Sources</h2>\n"
+                   "<p>\n");
 
-            pa_ioline_printf(c->line,
-                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n",
-                             sink->monitor_source->name, m, t);
+    PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
+        char *t, *m;
 
-            pa_xfree(t);
-            pa_xfree(m);
-        }
+        if (source->monitor_of)
+            continue;
+
+        t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+        m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
+
+        pa_ioline_printf(c->line,
+                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
+                         source->name, m, t);
+
+        pa_xfree(m);
+        pa_xfree(t);
 
-        pa_ioline_puts(c->line,
-                       "</p>\n"
-                       "<h2>Sources</h2>\n"
-                       "<p>\n");
+    }
+
+    pa_ioline_puts(c->line,
+                   "</p>\n"
+                   HTML_FOOTER);
 
-        PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
-            char *t, *m;
+    pa_ioline_defer_close(c->line);
+}
 
-            if (source->monitor_of)
-                continue;
+static void line_drain_callback(pa_ioline *l, void *userdata) {
+    struct connection *c;
 
-            t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
-            m = mimefy_and_stringify_sample_spec(&source->sample_spec, &source->channel_map);
+    pa_assert(l);
+    pa_assert_se(c = userdata);
 
-            pa_ioline_printf(c->line,
-                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n",
-                             source->name, m, t);
+    /* We don't need the line reader anymore, instead we need a real
+     * binary io channel */
+    pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
+    pa_iochannel_set_callback(c->io, io_callback, c);
 
-            pa_xfree(m);
-            pa_xfree(t);
+    pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
 
-        }
+    pa_ioline_unref(c->line);
+    c->line = NULL;
+}
 
-        pa_ioline_puts(c->line,
-                       "</p>\n"
-                       HTML_FOOTER);
+static void handle_listen_prefix(struct connection *c, const char *source_name) {
+    pa_source *source;
+    pa_source_output_new_data data;
+    pa_sample_spec ss;
+    pa_channel_map cm;
+    char *t;
+    size_t l;
 
-        pa_ioline_defer_close(c->line);
-    } else
+    pa_assert(c);
+    pa_assert(source_name);
+
+    pa_assert(c->line);
+    pa_assert(!c->io);
+
+    if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
+        html_response(c, 404, "Source not found", NULL);
+        return;
+    }
+
+    ss = source->sample_spec;
+    cm = source->channel_map;
+
+    pa_sample_spec_mimefy(&ss, &cm);
+
+    pa_source_output_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = c->module;
+    data.client = c->client;
+    data.source = source;
+    pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+    pa_source_output_new_data_set_sample_spec(&data, &ss);
+    pa_source_output_new_data_set_channel_map(&data, &cm);
+
+    pa_source_output_new(&c->source_output, c->protocol->core, &data);
+    pa_source_output_new_data_done(&data);
+
+    if (!c->source_output) {
+        html_response(c, 403, "Cannot create source output", NULL);
+        return;
+    }
+
+    c->source_output->parent.process_msg = source_output_process_msg;
+    c->source_output->push = source_output_push_cb;
+    c->source_output->kill = source_output_kill_cb;
+    c->source_output->get_latency = source_output_get_latency_cb;
+    c->source_output->userdata = c;
+
+    pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
+
+    l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
+    c->output_memblockq = pa_memblockq_new(
+            0,
+            l,
+            0,
+            pa_frame_size(&ss),
+            1,
+            0,
+            0,
+            NULL);
+
+    pa_source_output_put(c->source_output);
+
+    t = pa_sample_spec_to_mime_type(&ss, &cm);
+    http_response(c, 200, "OK", t);
+    pa_xfree(t);
+
+    if(c->method == METHOD_HEAD) {
+        connection_unlink(c);
+        return;
+    }
+    pa_ioline_set_callback(c->line, NULL, NULL);
+
+    if (pa_ioline_is_drained(c->line))
+        line_drain_callback(c->line, c);
+    else
+        pa_ioline_set_drain_callback(c->line, line_drain_callback, c);
+}
+
+static void handle_url(struct connection *c) {
+    pa_assert(c);
+
+    pa_log_debug("Request for %s", c->url);
+
+    if (pa_streq(c->url, URL_ROOT))
+        handle_root(c);
+    else if (pa_streq(c->url, URL_CSS))
+        handle_css(c);
+    else if (pa_streq(c->url, URL_STATUS))
+        handle_status(c);
+    else if (pa_streq(c->url, URL_LISTEN))
+        handle_listen(c);
+    else if (pa_startswith(c->url, URL_LISTEN_SOURCE))
+        handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1);
+    else
         html_response(c, 404, "Not Found", NULL);
 }
 
@@ -489,10 +642,15 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
 
     switch (c->state) {
         case STATE_REQUEST_LINE: {
-            if (!pa_startswith(s, "GET "))
+            if (pa_startswith(s, "GET ")) {
+                c->method = METHOD_GET;
+                s +=4;
+            } else if (pa_startswith(s, "HEAD ")) {
+                c->method = METHOD_HEAD;
+                s +=5;
+            } else {
                 goto fail;
-
-            s +=4;
+            }
 
             c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
             c->state = STATE_MIME_HEADER;
@@ -519,11 +677,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
     return;
 
 fail:
-    internal_server_error(c);
+    html_response(c, 500, "Internal Server Error", NULL);
 }
 
 void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
     struct connection *c;
+    pa_client_new_data client_data;
+    char pname[128];
 
     pa_assert(p);
     pa_assert(io);
@@ -535,16 +695,36 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *
         return;
     }
 
-    c = pa_xnew(struct connection, 1);
+    c = pa_xnew0(struct connection, 1);
     c->protocol = p;
-    c->line = pa_ioline_new(io);
     c->state = STATE_REQUEST_LINE;
-    c->url = NULL;
     c->module = m;
 
+    c->line = pa_ioline_new(io);
     pa_ioline_set_callback(c->line, line_callback, c);
 
+    pa_client_new_data_init(&client_data);
+    client_data.module = c->module;
+    client_data.driver = __FILE__;
+    pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+    pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname);
+    pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname);
+    c->client = pa_client_new(p->core, &client_data);
+    pa_client_new_data_done(&client_data);
+
+    if (!c->client)
+        goto fail;
+
+    c->client->kill = client_kill_cb;
+    c->client->userdata = c;
+
     pa_idxset_put(p->connections, c, NULL);
+
+    return;
+
+fail:
+    if (c)
+        connection_unlink(c);
 }
 
 void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
@@ -564,7 +744,7 @@ static pa_http_protocol* http_protocol_new(pa_core *c) {
 
     pa_assert(c);
 
-    p = pa_xnew(pa_http_protocol, 1);
+    p = pa_xnew0(pa_http_protocol, 1);
     PA_REFCNT_INIT(p);
     p->core = c;
     p->connections = pa_idxset_new(NULL, NULL);
@@ -606,7 +786,32 @@ void pa_http_protocol_unref(pa_http_protocol *p) {
 
     pa_idxset_free(p->connections, NULL, NULL);
 
+    pa_strlist_free(p->servers);
+
     pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0);
 
     pa_xfree(p);
 }
+
+void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) {
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) >= 1);
+    pa_assert(name);
+
+    p->servers = pa_strlist_prepend(p->servers, name);
+}
+
+void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) {
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) >= 1);
+    pa_assert(name);
+
+    p->servers = pa_strlist_remove(p->servers, name);
+}
+
+pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) {
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+    return p->servers;
+}