From 84a92f2a88e9655a4d54410b37d9ca1d741646b9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2009 04:15:24 +0200 Subject: protocol-http: allow listening into sinks/sources via HTTP --- src/pulsecore/protocol-http.c | 505 ++++++++++++++++++++++++++++++++---------- 1 file changed, 394 insertions(+), 111 deletions(-) (limited to 'src/pulsecore/protocol-http.c') 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 #include #include +#include #include #include +#include #include +#include #include #include #include #include #include +#include #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 \ " \n" \ "\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, + "%s" + "%s\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) + "

"PACKAGE_NAME" "PACKAGE_VERSION"

\n" + "\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, + "
\n" + "

Show an extensive server status report

\n" + "

Monitor sinks and sources

\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, - "%s" - "%s\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) - "

"PACKAGE_NAME" "PACKAGE_VERSION"

\n" - "\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") + "

Sinks

\n" + "

\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, + "%s
\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, + "

\n" + "

Sources

\n" + "

\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, + "%s
\n", + source->name, m, t); + + pa_xfree(m); pa_xfree(t); - pa_ioline_puts(c->line, - "

\n" - "

Show an extensive server status report

\n" - "

Monitor sinks and sources

\n" - HTML_FOOTER); + } - pa_ioline_defer_close(c->line); + pa_ioline_puts(c->line, + "

\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") - "

Sinks

\n" - "

\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, - "%s
\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, - "

\n" - "

Sources

\n" - "

\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, - "%s
\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, - "

\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) { -- cgit