diff options
Diffstat (limited to '')
-rw-r--r-- | src/OutputList.cxx | 2 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 592 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 215 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 14 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx (renamed from src/output/httpd_output_plugin.c) | 235 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/httpd_client.c | 764 | ||||
-rw-r--r-- | src/output/httpd_client.h | 70 |
8 files changed, 911 insertions, 987 deletions
diff --git a/src/OutputList.cxx b/src/OutputList.cxx index 3e469385f..df064219f 100644 --- a/src/OutputList.cxx +++ b/src/OutputList.cxx @@ -24,7 +24,7 @@ #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" +#include "output/HttpdOutputPlugin.hxx" #include "output/jack_output_plugin.h" #include "output/mvp_output_plugin.h" #include "output/null_output_plugin.h" diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..33aad1919 --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "page.h" +#include "icy_server.h" +#include "glib_socket.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (write_source_id != 0) + g_source_remove(write_source_id); + + if (current_page != nullptr) + page_unref(current_page); + + for (auto page : pages) + page_unref(page); + } else + fifo_buffer_free(input); + + if (metadata) + page_unref(metadata); + + g_source_remove(read_source_id); + g_io_channel_unref(channel); +} + +void +HttpdClient::Close() +{ + httpd_output_remove_client(httpd, this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + write_source_id = 0; + current_page = nullptr; + + httpd_output_send_header(httpd, this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +char * +HttpdClient::ReadLine() +{ + assert(state != RESPONSE); + + const ScopeLock protect(httpd->mutex); + + size_t length; + const char *p = (const char *)fifo_buffer_read(input, &length); + if (p == nullptr) + /* empty input buffer */ + return nullptr; + + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == nullptr) + /* incomplete line */ + return nullptr; + + char *line = g_strndup(p, newline - p); + fifo_buffer_consume(input, newline - p + 1); + + /* remove trailing whitespace (e.g. '\r') */ + return g_strchomp(line); +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + status = g_io_channel_write_chars(channel, + buffer, strlen(buffer), + &bytes_written, &error); + + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +bool +HttpdClient::Received() +{ + assert(state != RESPONSE); + + char *line; + bool success; + + while ((line = ReadLine()) != nullptr) { + success = HandleLine(line); + g_free(line); + if (!success) { + assert(state != RESPONSE); + return false; + } + + if (state == RESPONSE) { + if (!fifo_buffer_is_empty(input)) { + g_warning("unexpected input from client"); + return false; + } + + fifo_buffer_free(input); + + return SendResponse(); + } + } + + return true; +} + +bool +HttpdClient::Read() +{ + size_t max_length; + GError *error = nullptr; + GIOStatus status; + gsize bytes_read; + + if (state == RESPONSE) { + /* the client has already sent the request, and he + must not send more */ + char buffer[1]; + + status = g_io_channel_read_chars(channel, buffer, + sizeof(buffer), &bytes_read, + nullptr); + if (status == G_IO_STATUS_NORMAL) + g_warning("unexpected input from client"); + + return false; + } + + char *p = (char *)fifo_buffer_write(input, &max_length); + if (p == nullptr) { + g_warning("buffer overflow"); + return false; + } + + status = g_io_channel_read_chars(channel, p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_append(input, bytes_read); + return Received(); + + case G_IO_STATUS_AGAIN: + /* try again later, after select() */ + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + g_warning("failed to read from client: %s", + error->message); + g_error_free(error); + return false; + } + + /* unreachable */ + return false; +} + +static gboolean +httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + HttpdClient *client = (HttpdClient *)data; + + if (condition == G_IO_IN && client->Read()) { + return true; + } else { + client->LockClose(); + return false; + } +} + +HttpdClient::HttpdClient(httpd_output *_httpd, int _fd, + bool _metadata_supported) + :httpd(_httpd), + channel(g_io_channel_new_socket(_fd)), + input(fifo_buffer_new(4096)), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(channel, true); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(channel, nullptr, nullptr); + /* we prefer to do buffering */ + g_io_channel_set_buffered(channel, false); + + read_source_id = g_io_add_watch(channel, + GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP), + httpd_client_in_event, this); +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page_unref(page); + pages.clear(); + + if (write_source_id != 0 && current_page == nullptr) { + g_source_remove(write_source_id); + write_source_id = 0; + } +} + +static GIOStatus +write_page_to_channel(GIOChannel *channel, + const struct page *page, size_t position, + gsize *bytes_written_r, GError **error) +{ + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + return g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + page->size - position, + bytes_written_r, error); +} + +static GIOStatus +write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, + size_t position, gint n, + gsize *bytes_written_r, GError **error) +{ + GIOStatus status; + + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + if (n == -1) { + status = write_page_to_channel (channel, page, position, + bytes_written_r, error); + } else { + status = g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + n, bytes_written_r, error); + } + + return status; +} + +int +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::Write() +{ + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (write_source_id == 0) + /* another thread has removed the event source while + this thread was waiting for httpd->mutex */ + return false; + + if (current_page == nullptr) { + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const gint bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + gint metadata_to_write; + + metadata_to_write = metadata_current_position; + + if (!metadata_sent) { + status = write_page_to_channel(channel, + metadata, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + struct page *empty_meta; + guchar empty_data = 0; + + empty_meta = page_new_copy(&empty_data, 1); + + status = write_page_to_channel(channel, + empty_meta, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (empty_meta->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + } + } + + bytes_written = 0; + } else { + status = write_n_bytes_to_channel(channel, current_page, + current_position, bytes_to_write, + &bytes_written, &error); + } + + switch (status) { + case G_IO_STATUS_NORMAL: + current_position += bytes_written; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += bytes_written; + + if (current_position >= current_page->size) { + page_unref(current_page); + current_page = nullptr; + + if (pages.empty()) { + /* all pages are sent: remove the + event source */ + write_source_id = 0; + + return false; + } + } + + return true; + + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +static gboolean +httpd_client_out_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, gpointer data) +{ + assert(condition == G_IO_OUT); + + HttpdClient *client = (HttpdClient *)data; + return client->Write(); +} + +void +HttpdClient::PushPage(struct page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page_ref(page); + pages.push_back(page); + + if (write_source_id == 0) + write_source_id = g_io_add_watch(channel, G_IO_OUT, + httpd_client_out_event, + this); +} + +void +HttpdClient::PushMetaData(struct page *page) +{ + if (metadata) { + page_unref(metadata); + metadata = nullptr; + } + + g_return_if_fail (page); + + page_ref(page); + metadata = page; + metadata_sent = false; +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..d3ed2746d --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "gcc.h" + +#include <glib.h> + +#include <list> + +#include <stddef.h> + +struct httpd_output; +struct page; + +class HttpdClient final { + /** + * The httpd output object this client is connected to. + */ + httpd_output *const httpd; + + /** + * The TCP socket. + */ + GIOChannel *channel; + + /** + * The GLib main loop source id for reading from the socket, + * and to detect errors. + */ + guint read_source_id; + + /** + * The GLib main loop source id for writing to the socket. If + * 0, then there is no event source currently (because there + * are no queued pages). + */ + guint write_source_id; + + /** + * For buffered reading. This pointer is only valid while the + * HTTP request is read. + */ + struct fifo_buffer *input; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #page objects to be sent to the client. + */ + std::list<page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #page which is currently being sent to the client. + */ + page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(httpd_output *httpd, int _fd, bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #httpd_output object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + bool Read(); + + /** + * Data has been received from the client and it is appended + * to the input buffer. + */ + bool Received(); + + /** + * Check if a complete line of input is present in the input + * buffer, and duplicates it. It is removed from the input + * buffer. The return value has to be freed with g_free(). + */ + char *ReadLine(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + int GetBytesTillMetaData() const; + + bool Write(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(page *page); +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9b..601c43162 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -25,14 +25,18 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "HttpdClient.hxx" #include "output_internal.h" #include "timer.h" +#include "thread/Mutex.hxx" #include <glib.h> +#include <forward_list> + #include <stdbool.h> -struct httpd_client; +class HttpdClient; struct httpd_output { struct audio_output base; @@ -65,7 +69,7 @@ struct httpd_output { * This mutex protects the listener socket and the client * list. */ - GMutex *mutex; + mutable Mutex mutex; /** * A #timer object to synchronize this output with the @@ -105,7 +109,7 @@ struct httpd_output { * A linked list containing all clients which are currently * connected. */ - GList *clients; + std::forward_list<HttpdClient> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -125,7 +129,7 @@ struct httpd_output { */ void httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); /** * Sends the encoder header to the client. This is called right after @@ -133,6 +137,6 @@ httpd_output_remove_client(struct httpd_output *httpd, */ void httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); #endif diff --git a/src/output/httpd_output_plugin.c b/src/output/HttpdOutputPlugin.cxx index 1d730df7f..c686104c1 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" +#include "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -60,9 +60,9 @@ httpd_output_quark(void) */ G_GNUC_PURE static bool -httpd_output_has_clients(const struct httpd_output *httpd) +httpd_output_has_clients(const httpd_output *httpd) { - return httpd->clients != NULL; + return !httpd->clients.empty(); } /** @@ -70,12 +70,10 @@ httpd_output_has_clients(const struct httpd_output *httpd) */ G_GNUC_PURE static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) +httpd_output_lock_has_clients(const httpd_output *httpd) { - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; + const ScopeLock protect(httpd->mutex); + return httpd_output_has_clients(httpd); } static void @@ -83,32 +81,28 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, int uid, void *ctx); static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) +httpd_output_bind(httpd_output *httpd, GError **error_r) { httpd->open = false; - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; + const ScopeLock protect(httpd->mutex); + return server_socket_open(httpd->server_socket, error_r); } static void -httpd_output_unbind(struct httpd_output *httpd) +httpd_output_unbind(httpd_output *httpd) { assert(!httpd->open); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); } static struct audio_output * httpd_output_init(const struct config_param *param, GError **error) { - struct httpd_output *httpd = g_new(struct httpd_output, 1); + httpd_output *httpd = new httpd_output(); if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { g_free(httpd); return NULL; @@ -174,50 +168,44 @@ httpd_output_init(const struct config_param *param, httpd->content_type = "application/octet-stream"; } - httpd->mutex = g_mutex_new(); - return &httpd->base; } static void httpd_output_finish(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd->metadata) page_unref(httpd->metadata); encoder_finish(httpd->encoder); server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); ao_base_finish(&httpd->base); - g_free(httpd); + delete httpd; } /** - * Creates a new #httpd_client object and adds it into the + * Creates a new #HttpdClient object and adds it into the * httpd_output.clients linked list. */ static void -httpd_client_add(struct httpd_output *httpd, int fd) +httpd_client_add(httpd_output *httpd, int fd) { - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients.emplace_front(httpd, fd, + httpd->encoder->plugin->tag == NULL); httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); + httpd->clients.front().PushMetaData(httpd->metadata); } static void httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, G_GNUC_UNUSED int uid, void *ctx) { - struct httpd_output *httpd = ctx; + httpd_output *httpd = (httpd_output *)ctx; /* the listener socket has become readable - a client has connected */ @@ -238,7 +226,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, progname, hostaddr); g_free(hostaddr); close_socket(fd); - g_mutex_unlock(httpd->mutex); return; } @@ -249,7 +236,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, (void)address_length; #endif /* HAVE_WRAP */ - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); if (fd >= 0) { /* can we allow additional client */ @@ -262,8 +249,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); } - - g_mutex_unlock(httpd->mutex); } /** @@ -271,7 +256,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, * as a new #page object. */ static struct page * -httpd_output_read_page(struct httpd_output *httpd) +httpd_output_read_page(httpd_output *httpd) { if (httpd->unflushed_input >= 65536) { /* we have fed a lot of input into the encoder, but it @@ -301,7 +286,7 @@ httpd_output_read_page(struct httpd_output *httpd) } static bool -httpd_output_encoder_open(struct httpd_output *httpd, +httpd_output_encoder_open(httpd_output *httpd, struct audio_format *audio_format, GError **error) { @@ -321,7 +306,7 @@ httpd_output_encoder_open(struct httpd_output *httpd, static bool httpd_output_enable(struct audio_output *ao, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; return httpd_output_bind(httpd, error_r); } @@ -329,7 +314,7 @@ httpd_output_enable(struct audio_output *ao, GError **error_r) static void httpd_output_disable(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; httpd_output_unbind(httpd); } @@ -338,82 +323,75 @@ static bool httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; + + assert(httpd->clients.empty()); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); /* open the encoder */ - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); + if (!httpd_output_encoder_open(httpd, audio_format, error)) return false; - } /* initialize other attributes */ - httpd->clients = NULL; httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); httpd->open = true; - g_mutex_unlock(httpd->mutex); return true; } static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void httpd_output_close(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); httpd->open = false; timer_free(httpd->timer); - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); + httpd->clients.clear(); if (httpd->header != NULL) page_unref(httpd->header); encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); } void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_remove_client(httpd_output *httpd, HttpdClient *client) { assert(httpd != NULL); + assert(httpd->clients_cnt > 0); assert(client != NULL); - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; + for (auto prev = httpd->clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != httpd->clients.end()); + if (&*i == client) { + httpd->clients.erase_after(prev); + httpd->clients_cnt--; + break; + } + } } void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_send_header(httpd_output *httpd, HttpdClient *client) { if (httpd->header != NULL) - httpd_client_send(client, httpd->header); + client->PushPage(httpd->header); } static unsigned httpd_output_delay(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { /* if there's no client and this output is paused, @@ -433,51 +411,35 @@ httpd_output_delay(struct audio_output *ao) : 0; } -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - /** * Broadcasts a page struct to all clients. */ static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) +httpd_output_broadcast_page(httpd_output *httpd, struct page *page) { assert(page != NULL); - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushPage(page); } /** * Broadcasts data from the encoder to all clients. */ static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) +httpd_output_encoder_to_clients(httpd_output *httpd) { - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); + httpd->mutex.lock(); + for (auto &client : httpd->clients) { + if (client.GetQueueSize() > 256 * 1024) { + g_debug("client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + httpd->mutex.unlock(); + struct page *page; while ((page = httpd_output_read_page(httpd)) != NULL) { httpd_output_broadcast_page(httpd, page); page_unref(page); @@ -485,7 +447,7 @@ httpd_output_encoder_to_clients(struct httpd_output *httpd) } static bool -httpd_output_encode_and_play(struct httpd_output *httpd, +httpd_output_encode_and_play(httpd_output *httpd, const void *chunk, size_t size, GError **error) { if (!encoder_write(httpd->encoder, chunk, size, error)) @@ -502,7 +464,7 @@ static size_t httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) @@ -519,10 +481,10 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, static bool httpd_output_pause(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; + static const char silence[1020] = { 0 }; return httpd_output_play(ao, silence, sizeof(silence), NULL) > 0; } else { @@ -531,18 +493,9 @@ httpd_output_pause(struct audio_output *ao) } static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void httpd_output_tag(struct audio_output *ao, const struct tag *tag) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; assert(tag != NULL); @@ -581,43 +534,37 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag) TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushMetaData(httpd->metadata); } } } static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void httpd_output_cancel(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); } const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, }; diff --git a/src/output/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx index d0eb1533f..c74d2bd4a 100644 --- a/src/output/httpd_output_plugin.h +++ b/src/output/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin httpd_output_plugin; diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 781858109..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_client.h" -#include "httpd_internal.h" -#include "util/fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index f0df829db..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <stdbool.h> -#include <stddef.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif |