diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/pulsecore/protocol-http.c | 505 | 
1 files changed, 394 insertions, 111 deletions
| diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index 08a70e50..64670244 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -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 @@ -26,16 +26,20 @@  #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 "protocol-http.h" @@ -46,7 +50,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" @@ -65,6 +69,10 @@  #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, @@ -73,7 +81,11 @@ enum state {  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;      pa_module *module; @@ -86,6 +98,163 @@ struct pa_http_protocol {      pa_idxset *connections;  }; +enum { +    SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +/* Called from main context */ +static void connection_unlink(struct connection *c) { +    pa_assert(c); + +    if (c->source_output) { +        pa_source_output_unlink(c->source_output); +        pa_source_output_unref(c->source_output); +    } + +    if (c->client) +        pa_client_free(c->client); + +    pa_xfree(c->url); + +    if (c->line) +        pa_ioline_unref(c->line); + +    if (c->io) +        pa_iochannel_free(c->io); + +    if (c->output_memblockq) +        pa_memblockq_free(c->output_memblockq); + +    pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + +    pa_xfree(c); +} + +/* Called from main context */ +static int do_write(struct connection *c) { +    pa_memchunk chunk; +    ssize_t r; +    void *p; + +    pa_assert(c); + +    if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) +        return 0; + +    pa_assert(chunk.memblock); +    pa_assert(chunk.length > 0); + +    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; +} + +/* Called from main context */ +static void do_work(struct connection *c) { +    pa_assert(c); + +    if (pa_iochannel_is_hungup(c->io)) +        goto fail; + +    if (pa_iochannel_is_writable(c->io)) +        if (do_write(c) < 0) +            goto fail; + +    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; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); + +    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; + +        default: +            return pa_source_output_process_msg(m, code, userdata, offset, chunk); +    } + +    return 0; +} + +/* 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); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { +    struct connection*c; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); + +    connection_unlink(c); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { +    struct connection*c; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); + +    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); + +    connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ +static void io_callback(pa_iochannel*io, void *userdata) { +    struct connection *c = userdata; + +    pa_assert(c); +    pa_assert(io); + +    do_work(c); +}  static pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) { @@ -317,162 +486,254 @@ static void html_response(      pa_ioline_defer_close(c->line);  } -static void internal_server_error(struct connection *c) { -    pa_assert(c); +static void html_print_field(pa_ioline *line, const char *left, const char *right) { +    char *eleft, *eright; -    html_response(c, 500, "Internal Server Error", NULL); +    eleft = escape_html(left); +    eright = escape_html(right); + +    pa_ioline_printf(line, +                     "<tr><td><b>%s</b></td>" +                     "<td>%s</td></tr>\n", eleft, eright); + +    pa_xfree(eleft); +    pa_xfree(eright);  } -static void connection_unlink(struct connection *c) { +static void handle_root(struct connection *c) { +    char *t; +      pa_assert(c); -    if (c->url) -        pa_xfree(c->url); +    http_response(c, 200, "OK", MIME_HTML); -    if (c->line) -        pa_ioline_unref(c->line); +    pa_ioline_puts(c->line, +                   HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) +                   "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" +                   "<table>\n"); -    pa_idxset_remove_by_data(c->protocol->connections, c, NULL); +    t = pa_get_user_name_malloc(); +    html_print_field(c->line, "User Name:", t); +    pa_xfree(t); -    pa_xfree(c); +    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_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); + +    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_defer_close(c->line);  } -static void html_print_field(pa_ioline *line, const char *left, const char *right) { -    char *eleft, *eright; +static void handle_css(struct connection *c) { +    pa_assert(c); -    eleft = escape_html(left); -    eright = escape_html(right); +    http_response(c, 200, "OK", MIME_CSS); -    pa_ioline_printf(line, -                     "<tr><td><b>%s</b></td>" -                     "<td>%s</td></tr>\n", eleft, eright); +    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_xfree(eleft); -    pa_xfree(eright); +    pa_ioline_defer_close(c->line);  } -static void handle_url(struct connection *c) { +static void handle_status(struct connection *c) { +    char *r; +      pa_assert(c); -    pa_log_debug("Request for %s", c->url); +    http_response(c, 200, "OK", MIME_TEXT); +    r = pa_full_status_string(c->protocol->core); +    pa_ioline_puts(c->line, r); +    pa_xfree(r); -    if (pa_streq(c->url, URL_ROOT)) { -        char *t; +    pa_ioline_defer_close(c->line); +} -        http_response(c, 200, "OK", MIME_HTML); +static void handle_listen(struct connection *c) { +    pa_source *source; +    pa_sink *sink; +    uint32_t idx; -        pa_ioline_puts(c->line, -                       HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) -                       "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" -                       "<table>\n"); +    http_response(c, 200, "OK", MIME_HTML); -        t = pa_get_user_name_malloc(); -        html_print_field(c->line, "User Name:", t); -        pa_xfree(t); +    pa_ioline_puts(c->line, +                   HTML_HEADER("Listen") +                   "<h2>Sinks</h2>\n" +                   "<p>\n"); -        t = pa_get_host_name_malloc(); -        html_print_field(c->line, "Host name:", t); -        pa_xfree(t); +    PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { +        char *t, *m; -        t = pa_machine_id(); -        html_print_field(c->line, "Machine ID:", t); -        pa_xfree(t); +        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_printf(c->line, +                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", +                         sink->monitor_source->name, m, t); -        t = pa_uname_string(); -        html_print_field(c->line, "System:", t);          pa_xfree(t); +        pa_xfree(m); +    } + +    pa_ioline_puts(c->line, +                   "</p>\n" +                   "<h2>Sources</h2>\n" +                   "<p>\n"); + +    PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { +        char *t, *m; + +        if (source->monitor_of) +            continue; -        t = pa_sprintf_malloc("%lu", (unsigned long) getpid()); -        html_print_field(c->line, "Process ID:", t); +        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_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, -                       "</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); +    pa_ioline_puts(c->line, +                   "</p>\n" +                   HTML_FOOTER); -    } else if (pa_streq(c->url, URL_CSS)) { -        http_response(c, 200, "OK", MIME_CSS); +    pa_ioline_defer_close(c->line); +} -        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"); +static void line_drain_callback(pa_ioline *l, void *userdata) { +    struct connection *c; -        pa_ioline_defer_close(c->line); +    pa_assert(l); +    pa_assert_se(c = userdata); -    } else if (pa_streq(c->url, URL_STATUS)) { -        char *r; +    /* 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); -        http_response(c, 200, "OK", MIME_TEXT); -        r = pa_full_status_string(c->protocol->core); -        pa_ioline_puts(c->line, r); -        pa_xfree(r); +    pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq)); -        pa_ioline_defer_close(c->line); +    pa_ioline_unref(c->line); +    c->line = NULL; +} -    } else if (pa_streq(c->url, URL_LISTEN)) { -        pa_source *source; -        pa_sink *sink; -        uint32_t idx; +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; -        http_response(c, 200, "OK", MIME_HTML); +    pa_assert(c); +    pa_assert(source_name); -        pa_ioline_puts(c->line, -                       HTML_HEADER("Listen") -                       "<h2>Sinks</h2>\n" -                       "<p>\n"); +    pa_assert(c->line); +    pa_assert(!c->io); -        PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { -            char *t, *m; +    if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { +        html_response(c, 404, "Source not found", NULL); +        return; +    } -            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); +    ss = source->sample_spec; +    cm = source->channel_map; -            pa_ioline_printf(c->line, -                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n", -                             sink->monitor_source->name, m, t); +    mimefy_sample_spec(&ss, &cm); -            pa_xfree(t); -            pa_xfree(m); -        } +    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_ioline_puts(c->line, -                       "</p>\n" -                       "<h2>Sources</h2>\n" -                       "<p>\n"); +    pa_source_output_new(&c->source_output, c->protocol->core, &data, 0); +    pa_source_output_new_data_done(&data); -        PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { -            char *t, *m; +    if (!c->source_output) { +        html_response(c, 403, "Cannot create source output", NULL); +        return; +    } -            if (source->monitor_of) -                continue; +    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; -            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_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); -            pa_ioline_printf(c->line, -                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n", -                             source->name, m, t); +    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_xfree(m); -            pa_xfree(t); +    pa_source_output_put(c->source_output); -        } +    t = sample_spec_to_mime_type(&ss, &cm); +    http_response(c, 200, "OK", t); +    pa_xfree(t); -        pa_ioline_puts(c->line, -                       "</p>\n" -                       HTML_FOOTER); +    pa_ioline_set_callback(c->line, NULL, NULL); -        pa_ioline_defer_close(c->line); -    } else +    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);  } @@ -519,11 +780,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 +798,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) { | 
