diff --git a/configure.ac b/configure.ac
index 595fbc27..25d6b5ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,7 +50,7 @@ AC_CHECK_HEADERS([sys/wait.h sys/param.h dirent.h getopt.h stdint.h], [],
[AC_MSG_ERROR([[Missing header required to build OwnTone]])])
AC_CHECK_HEADERS([time.h], [],
[AC_MSG_ERROR([[Missing header required to build OwnTone]])])
-AC_CHECK_FUNCS_ONCE([posix_fadvise pipe2])
+AC_CHECK_FUNCS_ONCE([posix_fadvise pipe2 syscall])
AC_CHECK_FUNCS([strptime strtok_r], [],
[AC_MSG_ERROR([[Missing function required to build OwnTone]])])
@@ -148,13 +148,10 @@ OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
[AC_MSG_RESULT([[runtime will tell]])])
])
-OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT], [libevent >= 2],
- [event_base_new], [event2/event.h],
- [dnl check for old version
- PKG_CHECK_EXISTS([libevent >= 2.1.4], [],
- [AC_DEFINE([HAVE_LIBEVENT2_OLD], 1,
- [Define to 1 if you have libevent 2 (<2.1.4)])])
- ])
+OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT], [libevent >= 2.1.4],
+ [event_base_new], [event2/event.h])
+OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT_PTHREADS], [libevent_pthreads],
+ [evthread_use_pthreads], [event2/thread.h])
dnl Check for evhttp_connection_get_peer() signature
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
@@ -269,11 +266,6 @@ OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libwebsockets support], [libwebsockets],
[libwebsockets >= 2.0.2])
AM_CONDITIONAL([COND_LIBWEBSOCKETS], [[test "x$with_libwebsockets" = "xyes"]])
-dnl Build with libevent_pthreads
-OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevent_pthreads support],
- [libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
- [evthread_use_pthreads], [event2/thread.h])
-
dnl Build with Avahi (or Bonjour if not)
OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [Avahi mDNS], [avahi], [AVAHI],
[avahi-client >= 0.6.24], [avahi_client_new], [avahi-client/client.h])
diff --git a/docs/building.md b/docs/building.md
index faf2c603..907fc746 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -224,7 +224,7 @@ Libraries:
from
- libconfuse
from
-- libevent 2.0+ (best with 2.1.4+)
+- libevent 2.1.4+
from
- MiniXML (aka mxml or libmxml)
from
diff --git a/src/Makefile.am b/src/Makefile.am
index fa01a0a9..a64b5299 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -92,14 +92,15 @@ owntone_SOURCES = main.c \
library.c library.h \
$(MDNS_SRC) mdns.h \
remote_pairing.c remote_pairing.h \
- httpd.c httpd.h \
- httpd_rsp.c httpd_rsp.h \
+ httpd_libevhttp.c \
+ httpd.c httpd.h httpd_internal.h \
+ httpd_rsp.c \
httpd_daap.c httpd_daap.h \
- httpd_dacp.c httpd_dacp.h \
- httpd_jsonapi.c httpd_jsonapi.h \
- httpd_streaming.c httpd_streaming.h \
- httpd_oauth.c httpd_oauth.h \
- httpd_artworkapi.c httpd_artworkapi.h \
+ httpd_dacp.c \
+ httpd_jsonapi.c \
+ httpd_streaming.c \
+ httpd_oauth.c \
+ httpd_artworkapi.c \
http.c http.h \
dmap_common.c dmap_common.h \
transcode.c transcode.h \
@@ -117,9 +118,11 @@ owntone_SOURCES = main.c \
outputs/rtp_common.h outputs/rtp_common.c \
outputs/raop.c outputs/airplay.c $(PAIR_AP_SRC) \
outputs/airplay_events.c outputs/airplay_events.h \
- outputs/streaming.c outputs/dummy.c outputs/fifo.c outputs/rcp.c \
+ outputs/streaming.c outputs/streaming.h \
+ outputs/dummy.c outputs/fifo.c outputs/rcp.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
+ evthr.c evthr.h \
$(SPOTIFY_SRC) \
$(LASTFM_SRC) \
$(MPD_SRC) \
diff --git a/src/artwork.c b/src/artwork.c
index f0c53a50..2135cd2e 100644
--- a/src/artwork.c
+++ b/src/artwork.c
@@ -582,20 +582,6 @@ size_calculate(int *dst_w, int *dst_h, int src_w, int src_h, int max_w, int max_
DPRINTF(E_DBG, L_ART, "Rescale required, destination width %d height %d\n", *dst_w, *dst_h);
}
-#ifdef HAVE_LIBEVENT2_OLD
-// This is not how this function is actually defined in libevent 2.1+, but it
-// works as a less optimal stand-in
-int
-evbuffer_add_buffer_reference(struct evbuffer *outbuf, struct evbuffer *inbuf)
-{
- uint8_t *buf = evbuffer_pullup(inbuf, -1);
- if (!buf)
- return -1;
-
- return evbuffer_add_reference(outbuf, buf, evbuffer_get_length(inbuf), NULL, NULL);
-}
-#endif
-
/*
* Either gets the artwork file given in "path" (rescaled if needed) or rescales
* the artwork given in "inbuf".
diff --git a/src/commands.c b/src/commands.c
index 7043bff1..822c65e5 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -63,8 +63,8 @@ command_cb_async(struct commands_base *cmdbase, struct command *cmd)
// Command is executed asynchronously
cmdstate = cmd->func(cmd->arg, &cmd->ret);
- // Only free arg if there are no pending events (used in worker.c)
- if (cmdstate != COMMAND_PENDING && cmd->arg)
+ // Only free arg if there are no pending events (used in httpd.c)
+ if (cmdstate != COMMAND_PENDING)
free(cmd->arg);
free(cmd);
diff --git a/src/dmap_common.c b/src/dmap_common.c
index cb091093..3d5e52e2 100644
--- a/src/dmap_common.c
+++ b/src/dmap_common.c
@@ -25,7 +25,6 @@
#include "db.h"
#include "misc.h"
-#include "httpd.h"
#include "logger.h"
#include "dmap_common.h"
#include "parsers/daap_parser.h"
@@ -360,31 +359,6 @@ dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errms
dmap_add_string(evbuf, "msts", errmsg);
}
-void
-dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg)
-{
- struct evbuffer *evbuf;
-
- if (!req)
- return;
-
- evbuf = evbuffer_new();
- if (!evbuf)
- {
- DPRINTF(E_LOG, L_DMAP, "Could not allocate evbuffer for DMAP error\n");
-
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
- return;
- }
-
- dmap_error_make(evbuf, container, errmsg);
-
- httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
-
- evbuffer_free(evbuf);
-}
-
-
int
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav)
{
diff --git a/src/dmap_common.h b/src/dmap_common.h
index 2babd621..51410924 100644
--- a/src/dmap_common.h
+++ b/src/dmap_common.h
@@ -78,10 +78,6 @@ dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval
void
dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg);
-void
-dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg);
-
-
int
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav);
diff --git a/src/evrtsp/rtsp.c b/src/evrtsp/rtsp.c
index 4d467df7..4f70d48d 100644
--- a/src/evrtsp/rtsp.c
+++ b/src/evrtsp/rtsp.c
@@ -63,17 +63,6 @@
#include "log.h"
#include "rtsp-internal.h"
-// For compability with libevent 2.0 (HAVE_LIBEVENT2_OLD)
-#if defined(_EVENT_HAVE_GETNAMEINFO)
-# define EVENT__HAVE_GETNAMEINFO 1
-#endif
-#if defined(_EVENT_HAVE_GETADDRINFO)
-# define EVENT__HAVE_GETADDRINFO 1
-#endif
-#if defined(_EVENT_HAVE_STRSEP)
-# define EVENT__HAVE_STRSEP 1
-#endif
-
#ifndef EVENT__HAVE_GETNAMEINFO
#define NI_MAXSERV 32
#define NI_MAXHOST 1025
diff --git a/src/evthr.c b/src/evthr.c
new file mode 100644
index 00000000..0c236cdc
--- /dev/null
+++ b/src/evthr.c
@@ -0,0 +1,467 @@
+/*
+------------------- Thread handling borrowed from libevhtp ---------------------
+
+BSD 3-Clause License
+
+Copyright (c) 2010-2018, Mark Ellzey, Nathan French, Marcus Sundberg
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include "evthr.h"
+
+#ifndef TAILQ_FOREACH_SAFE
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+#endif
+
+#define _evthr_read(thr, cmd, sock) \
+ (recv(sock, cmd, sizeof(struct evthr_cmd), 0) == sizeof(struct evthr_cmd)) ? 1 : 0
+
+#define EVTHR_SHARED_PIPE 1
+
+struct evthr_cmd {
+ uint8_t stop;
+ void *args;
+ evthr_cb cb;
+} __attribute__((packed));
+
+struct evthr_pool {
+#ifdef EVTHR_SHARED_PIPE
+ int rdr;
+ int wdr;
+#endif
+ int nthreads;
+ TAILQ_HEAD(evthr_pool_slist, evthr) threads;
+};
+
+struct evthr {
+ int rdr;
+ int wdr;
+ char err;
+ struct event *event;
+ struct event_base *evbase;
+ pthread_mutex_t lock;
+ pthread_t *thr;
+ evthr_init_cb init_cb;
+ evthr_exit_cb exit_cb;
+ void *arg;
+ void *aux;
+#ifdef EVTHR_SHARED_PIPE
+ int pool_rdr;
+ struct event *shared_pool_ev;
+#endif
+ TAILQ_ENTRY(evthr) next;
+};
+
+
+static void
+_evthr_read_cmd(evutil_socket_t sock, short which, void *args)
+{
+ struct evthr *thread;
+ struct evthr_cmd cmd;
+ int stopped;
+
+ if (!(thread = (struct evthr *)args)) {
+ return;
+ }
+
+ stopped = 0;
+
+ if (_evthr_read(thread, &cmd, sock) == 1) {
+ stopped = cmd.stop;
+
+ if (cmd.cb != NULL) {
+ (cmd.cb)(thread, cmd.args, thread->arg);
+ }
+ }
+
+ if (stopped == 1) {
+ event_base_loopbreak(thread->evbase);
+ }
+
+ return;
+}
+
+static void *
+_evthr_loop(void *args)
+{
+ struct evthr *thread;
+
+ if (!(thread = (struct evthr *)args)) {
+ return NULL;
+ }
+
+ if (thread == NULL || thread->thr == NULL) {
+ pthread_exit(NULL);
+ }
+
+ thread->evbase = event_base_new();
+ thread->event = event_new(thread->evbase, thread->rdr,
+ EV_READ | EV_PERSIST, _evthr_read_cmd, args);
+
+ event_add(thread->event, NULL);
+
+#ifdef EVTHR_SHARED_PIPE
+ if (thread->pool_rdr > 0) {
+ thread->shared_pool_ev = event_new(thread->evbase, thread->pool_rdr,
+ EV_READ | EV_PERSIST, _evthr_read_cmd, args);
+ event_add(thread->shared_pool_ev, NULL);
+ }
+#endif
+
+ pthread_mutex_lock(&thread->lock);
+ if (thread->init_cb != NULL) {
+ (thread->init_cb)(thread, thread->arg);
+ }
+
+ pthread_mutex_unlock(&thread->lock);
+
+ event_base_loop(thread->evbase, 0);
+
+ pthread_mutex_lock(&thread->lock);
+ if (thread->exit_cb != NULL) {
+ (thread->exit_cb)(thread, thread->arg);
+ }
+
+ pthread_mutex_unlock(&thread->lock);
+
+ pthread_exit(NULL);
+}
+
+static enum evthr_res
+evthr_defer(struct evthr *thread, evthr_cb cb, void *arg)
+{
+ struct evthr_cmd cmd = {
+ .cb = cb,
+ .args = arg,
+ .stop = 0
+ };
+
+ if (send(thread->wdr, &cmd, sizeof(cmd), 0) <= 0) {
+ return EVTHR_RES_RETRY;
+ }
+
+ return EVTHR_RES_OK;
+}
+
+static enum evthr_res
+evthr_stop(struct evthr *thread)
+{
+ struct evthr_cmd cmd = {
+ .cb = NULL,
+ .args = NULL,
+ .stop = 1
+ };
+
+ if (send(thread->wdr, &cmd, sizeof(struct evthr_cmd), 0) < 0) {
+ return EVTHR_RES_RETRY;
+ }
+
+ pthread_join(*thread->thr, NULL);
+ return EVTHR_RES_OK;
+}
+
+struct event_base *
+evthr_get_base(struct evthr * thr)
+{
+ return thr ? thr->evbase : NULL;
+}
+
+void
+evthr_set_aux(struct evthr *thr, void *aux)
+{
+ if (thr) {
+ thr->aux = aux;
+ }
+}
+
+void *
+evthr_get_aux(struct evthr *thr)
+{
+ return thr ? thr->aux : NULL;
+}
+
+static void
+evthr_free(struct evthr *thread)
+{
+ if (thread == NULL) {
+ return;
+ }
+
+ if (thread->rdr > 0) {
+ close(thread->rdr);
+ }
+
+ if (thread->wdr > 0) {
+ close(thread->wdr);
+ }
+
+ if (thread->thr) {
+ free(thread->thr);
+ }
+
+ if (thread->event) {
+ event_free(thread->event);
+ }
+
+#ifdef EVTHR_SHARED_PIPE
+ if (thread->shared_pool_ev) {
+ event_free(thread->shared_pool_ev);
+ }
+#endif
+
+ if (thread->evbase) {
+ event_base_free(thread->evbase);
+ }
+
+ free(thread);
+}
+
+static struct evthr *
+evthr_wexit_new(evthr_init_cb init_cb, evthr_exit_cb exit_cb, void *args)
+{
+ struct evthr *thread;
+ int fds[2];
+
+ if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
+ return NULL;
+ }
+
+ evutil_make_socket_nonblocking(fds[0]);
+ evutil_make_socket_nonblocking(fds[1]);
+
+ if (!(thread = calloc(1, sizeof(struct evthr)))) {
+ return NULL;
+ }
+
+ thread->thr = malloc(sizeof(pthread_t));
+ thread->arg = args;
+ thread->rdr = fds[0];
+ thread->wdr = fds[1];
+
+ thread->init_cb = init_cb;
+ thread->exit_cb = exit_cb;
+
+ if (pthread_mutex_init(&thread->lock, NULL)) {
+ evthr_free(thread);
+ return NULL;
+ }
+
+ return thread;
+}
+
+static int
+evthr_start(struct evthr *thread)
+{
+ if (thread == NULL || thread->thr == NULL) {
+ return -1;
+ }
+
+ if (pthread_create(thread->thr, NULL, _evthr_loop, (void *)thread)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+evthr_pool_free(struct evthr_pool *pool)
+{
+ struct evthr *thread;
+ struct evthr *save;
+
+ if (pool == NULL) {
+ return;
+ }
+
+ TAILQ_FOREACH_SAFE(thread, &pool->threads, next, save) {
+ TAILQ_REMOVE(&pool->threads, thread, next);
+
+ evthr_free(thread);
+ }
+
+ free(pool);
+}
+
+enum evthr_res
+evthr_pool_stop(struct evthr_pool *pool)
+{
+ struct evthr *thr;
+ struct evthr *save;
+
+ if (pool == NULL) {
+ return EVTHR_RES_FATAL;
+ }
+
+ TAILQ_FOREACH_SAFE(thr, &pool->threads, next, save) {
+ evthr_stop(thr);
+ }
+
+ return EVTHR_RES_OK;
+}
+
+static inline int
+get_backlog_(struct evthr *thread)
+{
+ int backlog = 0;
+
+ ioctl(thread->rdr, FIONREAD, &backlog);
+
+ return (int)(backlog / sizeof(struct evthr_cmd));
+}
+
+enum evthr_res
+evthr_pool_defer(struct evthr_pool *pool, evthr_cb cb, void *arg)
+{
+#ifdef EVTHR_SHARED_PIPE
+ struct evthr_cmd cmd = {
+ .cb = cb,
+ .args = arg,
+ .stop = 0
+ };
+
+ if (send(pool->wdr, &cmd, sizeof(cmd), 0) == -1) {
+ return EVTHR_RES_RETRY;
+ }
+
+ return EVTHR_RES_OK;
+#endif
+ struct evthr *thread = NULL;
+ struct evthr *min_thread = NULL;
+ int min_backlog = 0;
+
+ if (pool == NULL) {
+ return EVTHR_RES_FATAL;
+ }
+
+ if (cb == NULL) {
+ return EVTHR_RES_NOCB;
+ }
+
+ TAILQ_FOREACH(thread, &pool->threads, next) {
+ int backlog = get_backlog_(thread);
+
+ if (backlog == 0) {
+ min_thread = thread;
+ break;
+ }
+
+ if (min_thread == NULL || backlog < min_backlog) {
+ min_thread = thread;
+ min_backlog = backlog;
+ }
+ }
+
+ return evthr_defer(min_thread, cb, arg);
+}
+
+struct evthr_pool *
+evthr_pool_wexit_new(int nthreads, evthr_init_cb init_cb, evthr_exit_cb exit_cb, void *shared)
+{
+ struct evthr_pool *pool;
+ int i;
+
+#ifdef EVTHR_SHARED_PIPE
+ int fds[2];
+#endif
+
+ if (nthreads == 0) {
+ return NULL;
+ }
+
+ if (!(pool = calloc(1, sizeof(struct evthr_pool)))) {
+ return NULL;
+ }
+
+ pool->nthreads = nthreads;
+ TAILQ_INIT(&pool->threads);
+
+#ifdef EVTHR_SHARED_PIPE
+ if (evutil_socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == -1) {
+ return NULL;
+ }
+
+ evutil_make_socket_nonblocking(fds[0]);
+ evutil_make_socket_nonblocking(fds[1]);
+
+ pool->rdr = fds[0];
+ pool->wdr = fds[1];
+#endif
+
+ for (i = 0; i < nthreads; i++) {
+ struct evthr * thread;
+
+ if (!(thread = evthr_wexit_new(init_cb, exit_cb, shared))) {
+ evthr_pool_free(pool);
+ return NULL;
+ }
+
+#ifdef EVTHR_SHARED_PIPE
+ thread->pool_rdr = fds[0];
+#endif
+
+ TAILQ_INSERT_TAIL(&pool->threads, thread, next);
+ }
+
+ return pool;
+}
+
+int
+evthr_pool_start(struct evthr_pool *pool)
+{
+ struct evthr *evthr = NULL;
+
+ if (pool == NULL) {
+ return -1;
+ }
+
+ TAILQ_FOREACH(evthr, &pool->threads, next) {
+ if (evthr_start(evthr) < 0) {
+ return -1;
+ }
+
+ usleep(5000);
+ }
+
+ return 0;
+}
diff --git a/src/evthr.h b/src/evthr.h
new file mode 100644
index 00000000..7f724f93
--- /dev/null
+++ b/src/evthr.h
@@ -0,0 +1,43 @@
+#ifndef __EVTHR_H__
+#define __EVTHR_H__
+
+enum evthr_res {
+ EVTHR_RES_OK = 0,
+ EVTHR_RES_BACKLOG,
+ EVTHR_RES_RETRY,
+ EVTHR_RES_NOCB,
+ EVTHR_RES_FATAL
+};
+
+struct evthr_pool;
+struct evthr;
+
+typedef void (*evthr_cb)(struct evthr *thr, void *cmd_arg, void *shared);
+typedef void (*evthr_init_cb)(struct evthr *thr, void *shared);
+typedef void (*evthr_exit_cb)(struct evthr *thr, void *shared);
+
+struct event_base *
+evthr_get_base(struct evthr *thr);
+
+void
+evthr_set_aux(struct evthr *thr, void *aux);
+
+void *
+evthr_get_aux(struct evthr *thr);
+
+enum evthr_res
+evthr_pool_defer(struct evthr_pool *pool, evthr_cb cb, void *arg);
+
+struct evthr_pool *
+evthr_pool_wexit_new(int nthreads, evthr_init_cb init_cb, evthr_exit_cb exit_cb, void *shared);
+
+void
+evthr_pool_free(struct evthr_pool *pool);
+
+enum evthr_res
+evthr_pool_stop(struct evthr_pool *pool);
+
+int
+evthr_pool_start(struct evthr_pool *pool);
+
+#endif /* !__EVTHR_H__ */
diff --git a/src/http.c b/src/http.c
index 566b64b4..6a1604d4 100644
--- a/src/http.c
+++ b/src/http.c
@@ -40,7 +40,6 @@
#include
#include "http.h"
-#include "httpd.h"
#include "logger.h"
#include "misc.h"
#include "conffile.h"
@@ -240,9 +239,11 @@ http_form_urlencode(struct keyval *kv)
int
http_stream_setup(char **stream, const char *url)
{
+ CURLU *url_handle;
+ CURLUcode rc;
struct http_client_ctx ctx;
- struct httpd_uri_parsed *parsed;
struct evbuffer *evbuf;
+ char *path;
const char *ext;
char *line;
char *pos;
@@ -253,17 +254,28 @@ http_stream_setup(char **stream, const char *url)
*stream = NULL;
- parsed = httpd_uri_parse(url);
- if (!parsed)
+ CHECK_NULL(L_HTTP, url_handle = curl_url());
+
+ rc = curl_url_set(url_handle, CURLUPART_URL, url, 0);
+ if (rc != 0)
{
DPRINTF(E_LOG, L_HTTP, "Couldn't parse internet playlist: '%s'\n", url);
+ curl_url_cleanup(url_handle);
return -1;
}
- // parsed->path does not include query or fragment, so should work with any url's
+ rc = curl_url_get(url_handle, CURLUPART_PATH, &path, 0);
+ if (rc != 0)
+ {
+ DPRINTF(E_LOG, L_HTTP, "Couldn't find internet playlist path: '%s'\n", url);
+ curl_url_cleanup(url_handle);
+ return -1;
+ }
+
+ // path does not include query or fragment, so should work with any url's
// e.g. http://yp.shoutcast.com/sbin/tunein-station.pls?id=99179772#Air Jazz
pl_format = PLAYLIST_UNK;
- if (parsed->path && (ext = strrchr(parsed->path, '.')))
+ if (path && (ext = strrchr(path, '.')))
{
if (strcasecmp(ext, ".m3u") == 0)
pl_format = PLAYLIST_M3U;
@@ -271,7 +283,9 @@ http_stream_setup(char **stream, const char *url)
pl_format = PLAYLIST_PLS;
}
- httpd_uri_free(parsed);
+ curl_free(path);
+ curl_url_cleanup(url_handle);
+
if (pl_format==PLAYLIST_UNK)
{
diff --git a/src/httpd.c b/src/httpd.c
index 397ccb72..8a5087bf 100644
--- a/src/httpd.c
+++ b/src/httpd.c
@@ -27,25 +27,20 @@
#include
#include
#include
-#include
#include
#include
-#include
#include
#include
#include
#include
-#ifdef HAVE_EVENTFD
-# include
+#ifdef HAVE_SYSCALL
+#include // get thread ID
#endif
+
#include
-#include
-#include
-#ifdef HAVE_LIBEVENT2_OLD
-# include
-# include
-#endif
+
+#include
#include
#include "logger.h"
@@ -53,14 +48,9 @@
#include "conffile.h"
#include "misc.h"
#include "worker.h"
+#include "evthr.h"
#include "httpd.h"
-#include "httpd_rsp.h"
-#include "httpd_daap.h"
-#include "httpd_dacp.h"
-#include "httpd_jsonapi.h"
-#include "httpd_streaming.h"
-#include "httpd_oauth.h"
-#include "httpd_artworkapi.h"
+#include "httpd_internal.h"
#include "transcode.h"
#ifdef LASTFM
# include "lastfm.h"
@@ -69,7 +59,6 @@
# include "websocket.h"
#endif
-
#define STREAM_CHUNK_SIZE (64 * 1024)
#define ERR_PAGE "\n\n" \
"%d %s\n" \
@@ -81,6 +70,26 @@
#define HTTPD_STREAM_BPS 16
#define HTTPD_STREAM_CHANNELS 2
+extern struct httpd_module httpd_dacp;
+extern struct httpd_module httpd_daap;
+extern struct httpd_module httpd_jsonapi;
+extern struct httpd_module httpd_artworkapi;
+extern struct httpd_module httpd_streaming;
+extern struct httpd_module httpd_oauth;
+extern struct httpd_module httpd_rsp;
+
+// Must be in sync with enum httpd_modules
+static struct httpd_module *httpd_modules[] = {
+ &httpd_dacp,
+ &httpd_daap,
+ &httpd_jsonapi,
+ &httpd_artworkapi,
+ &httpd_streaming,
+ &httpd_oauth,
+ &httpd_rsp,
+ NULL
+};
+
struct content_type_map {
char *ext;
@@ -88,9 +97,7 @@ struct content_type_map {
};
struct stream_ctx {
- struct evhttp_request *req;
- uint8_t *buf;
- struct evbuffer *evbuf;
+ struct httpd_request *hreq;
struct event *ev;
int id;
int fd;
@@ -116,27 +123,26 @@ static const struct content_type_map ext2ctype[] =
{ NULL, NULL }
};
-static const char *http_reply_401 = "401 UnauthorizedAuthorization required";
-
static char webroot_directory[PATH_MAX];
-struct event_base *evbase_httpd;
-#ifdef HAVE_EVENTFD
-static int exit_efd;
-#else
-static int exit_pipe[2];
-#endif
-static int httpd_exit;
-static struct event *exitev;
-static struct evhttp *evhttpd;
-static pthread_t tid_httpd;
-
-static const char *allow_origin;
+static const char *httpd_allow_origin;
static int httpd_port;
-#ifdef HAVE_LIBEVENT2_OLD
-struct stream_ctx *g_st;
-#endif
+
+// The server is designed around a single thread listening for requests. When
+// received, the request is passed to a thread from the worker pool, where a
+// handler will process it and prepare a response for the httpd thread to send
+// back. The idea is that the httpd thread never blocks. The handler in the
+// worker thread can block, but shouldn't hold the thread if it is a long-
+// running request (e.g. a long poll), because then we can run out of worker
+// threads. The handler should use events to avoid this. Handlers, that are non-
+// blocking and where the response must not be delayed can use
+// HTTPD_HANDLER_REALTIME, then the httpd thread calls it directly (sync)
+// instead of the async worker. In short, you shouldn't need to increase the
+// below.
+#define THREADPOOL_NTHREADS 1
+
+static struct evthr_pool *httpd_threadpool;
/* -------------------------------- HELPERS --------------------------------- */
@@ -167,107 +173,178 @@ scrobble_cb(void *arg)
}
#endif
-/*
- * This disabled in the commit after d8cdc89 because my tests work fine without
- * it, and it seems that nowadays iTunes and Remote encodes the query just fine.
- * However, I'm keeping it around for a while in case problems show up. If you
- * are from the future, you can probably safely remove it for good.
- *
-static char *
-httpd_fixup_uri(struct evhttp_request *req)
+
+/* --------------------------- MODULES INTERFACE ---------------------------- */
+
+static void
+modules_handlers_unset(struct httpd_uri_map *uri_map)
{
- struct evkeyvalq *headers;
- const char *ua;
- const char *uri;
- const char *u;
- const char *q;
- char *fixed;
- char *f;
- int len;
+ struct httpd_uri_map *uri;
- uri = evhttp_request_get_uri(req);
- if (!uri)
- return NULL;
-
- // No query string, nothing to do
- q = strchr(uri, '?');
- if (!q)
- return strdup(uri);
-
- headers = evhttp_request_get_input_headers(req);
- ua = evhttp_find_header(headers, "User-Agent");
- if (!ua)
- return strdup(uri);
-
- if ((strncmp(ua, "iTunes", strlen("iTunes")) != 0)
- && (strncmp(ua, "Remote", strlen("Remote")) != 0)
- && (strncmp(ua, "Roku", strlen("Roku")) != 0))
- return strdup(uri);
-
- // Reencode + as %2B and space as + in the query,
- // which iTunes and Roku devices don't do
- len = strlen(uri);
-
- u = q;
- while (*u)
+ for (uri = uri_map; uri->preg; uri++)
{
- if (*u == '+')
- len += 2;
-
- u++;
+ regfree(uri->preg); // Frees allocation by regcomp
+ free(uri->preg); // Frees our own calloc
}
+}
- fixed = (char *)malloc(len + 1);
- if (!fixed)
- return NULL;
+static int
+modules_handlers_set(struct httpd_uri_map *uri_map)
+{
+ struct httpd_uri_map *uri;
+ char buf[64];
+ int ret;
- strncpy(fixed, uri, q - uri);
-
- f = fixed + (q - uri);
- while (*q)
+ for (uri = uri_map; uri->handler; uri++)
{
- switch (*q)
+ uri->preg = calloc(1, sizeof(regex_t));
+ if (!uri->preg)
{
- case '+':
- *f = '%';
- f++;
- *f = '2';
- f++;
- *f = 'B';
- break;
-
- case ' ':
- *f = '+';
- break;
-
- default:
- *f = *q;
- break;
+ DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, out of memory");
+ goto error;
}
- q++;
- f++;
+ ret = regcomp(uri->preg, uri->regexp, REG_EXTENDED | REG_NOSUB);
+ if (ret != 0)
+ {
+ regerror(ret, uri->preg, buf, sizeof(buf));
+ DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, regexp error: %s\n", buf);
+ goto error;
+ }
}
- *f = '\0';
+ return 0;
- return fixed;
+ error:
+ modules_handlers_unset(uri_map);
+ return -1;
+}
+
+static int
+modules_init(void)
+{
+ struct httpd_module **ptr;
+ struct httpd_module *m;
+
+ for (ptr = httpd_modules; *ptr; ptr++)
+ {
+ m = *ptr;
+ m->initialized = (!m->init || m->init() == 0);
+ if (!m->initialized)
+ {
+ DPRINTF(E_FATAL, L_HTTPD, "%s init failed\n", m->name);
+ return -1;
+ }
+
+ if (modules_handlers_set(m->handlers) != 0)
+ {
+ DPRINTF(E_FATAL, L_HTTPD, "%s handler configuration failed\n", m->name);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+modules_deinit(void)
+{
+ struct httpd_module **ptr;
+ struct httpd_module *m;
+
+ for (ptr = httpd_modules; *ptr; ptr++)
+ {
+ m = *ptr;
+ if (m->initialized && m->deinit)
+ m->deinit();
+
+ modules_handlers_unset(m->handlers);
+ }
+}
+
+static struct httpd_module *
+modules_search(const char *path)
+{
+ struct httpd_module **ptr;
+ struct httpd_module *m;
+ const char **test;
+ bool is_found = false;
+
+ for (ptr = httpd_modules; *ptr; ptr++)
+ {
+ m = *ptr;
+ if (!m->subpaths || !m->request)
+ continue;
+
+ for (test = m->subpaths; *test && !is_found; test++)
+ is_found = (strncmp(path, *test, strlen(*test)) == 0);
+
+ for (test = m->fullpaths; *test && !is_found; test++)
+ is_found = (strcmp(path, *test) == 0);
+
+ if (is_found)
+ return m;
+ }
+
+ return NULL;
}
-*/
/* --------------------------- REQUEST HELPERS ------------------------------ */
+static void
+cors_headers_add(struct httpd_request *hreq, const char *allow_origin)
+{
+ if (allow_origin)
+ httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin);
+
+ httpd_header_add(hreq->out_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
+ httpd_header_add(hreq->out_headers, "Access-Control-Allow-Headers", "authorization");
+}
+
+static bool
+is_cors_preflight(struct httpd_request *hreq, const char *allow_origin)
+{
+ return ( hreq->method == HTTPD_METHOD_OPTIONS && hreq->in_headers && allow_origin &&
+ httpd_header_find(hreq->in_headers, "Origin") &&
+ httpd_header_find(hreq->in_headers, "Access-Control-Request-Method") );
+}
void
-httpd_redirect_to(struct evhttp_request *req, const char *path)
+httpd_request_handler_set(struct httpd_request *hreq)
{
- struct evkeyvalq *headers;
+ struct httpd_uri_map *map;
+ int ret;
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "Location", path);
+ // Path with e.g. /api -> JSON module
+ hreq->module = modules_search(hreq->path);
+ if (!hreq->module)
+ {
+ return;
+ }
- httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
+ for (map = hreq->module->handlers; map->handler; map++)
+ {
+ // Check if handler supports the current http request method
+ if (map->method && hreq->method && !(map->method & hreq->method))
+ continue;
+
+ ret = regexec(map->preg, hreq->path, 0, NULL, 0);
+ if (ret != 0)
+ continue;
+
+ hreq->handler = map->handler;
+ hreq->is_async = !(map->flags & HTTPD_HANDLER_REALTIME);
+ break;
+ }
+}
+
+void
+httpd_redirect_to(struct httpd_request *hreq, const char *path)
+{
+ httpd_header_add(hreq->out_headers, "Location", path);
+
+ httpd_send_reply(hreq, HTTP_MOVETEMP, "Moved", HTTPD_SEND_NO_GZIP);
}
/*
@@ -282,23 +359,19 @@ httpd_redirect_to(struct evhttp_request *req, const char *path)
* @return True if the given ETag matches the request-header-value "If-None-Match", otherwise false
*/
bool
-httpd_request_etag_matches(struct evhttp_request *req, const char *etag)
+httpd_request_etag_matches(struct httpd_request *hreq, const char *etag)
{
- struct evkeyvalq *input_headers;
- struct evkeyvalq *output_headers;
const char *none_match;
- input_headers = evhttp_request_get_input_headers(req);
- none_match = evhttp_find_header(input_headers, "If-None-Match");
+ none_match = httpd_header_find(hreq->in_headers, "If-None-Match");
// Return not modified, if given timestamp matches "If-Modified-Since" request header
if (none_match && (strcasecmp(etag, none_match) == 0))
return true;
// Add cache headers to allow client side caching
- output_headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(output_headers, "Cache-Control", "private,no-cache,max-age=0");
- evhttp_add_header(output_headers, "ETag", etag);
+ httpd_header_add(hreq->out_headers, "Cache-Control", "private,no-cache,max-age=0");
+ httpd_header_add(hreq->out_headers, "ETag", etag);
return false;
}
@@ -315,16 +388,13 @@ httpd_request_etag_matches(struct evhttp_request *req, const char *etag)
* @return True if the given timestamp matches the request-header-value "If-Modified-Since", otherwise false
*/
bool
-httpd_request_not_modified_since(struct evhttp_request *req, time_t mtime)
+httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime)
{
- struct evkeyvalq *input_headers;
- struct evkeyvalq *output_headers;
char last_modified[1000];
const char *modified_since;
struct tm timebuf;
- input_headers = evhttp_request_get_input_headers(req);
- modified_since = evhttp_find_header(input_headers, "If-Modified-Since");
+ modified_since = httpd_header_find(hreq->in_headers, "If-Modified-Since");
strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", gmtime_r(&mtime, &timebuf));
@@ -333,38 +403,31 @@ httpd_request_not_modified_since(struct evhttp_request *req, time_t mtime)
return true;
// Add cache headers to allow client side caching
- output_headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(output_headers, "Cache-Control", "private,no-cache,max-age=0");
- evhttp_add_header(output_headers, "Last-Modified", last_modified);
+ httpd_header_add(hreq->out_headers, "Cache-Control", "private,no-cache,max-age=0");
+ httpd_header_add(hreq->out_headers, "Last-Modified", last_modified);
return false;
}
void
-httpd_response_not_cachable(struct evhttp_request *req)
+httpd_response_not_cachable(struct httpd_request *hreq)
{
- struct evkeyvalq *output_headers;
-
- output_headers = evhttp_request_get_output_headers(req);
-
// Remove potentially set cache control headers
- evhttp_remove_header(output_headers, "Cache-Control");
- evhttp_remove_header(output_headers, "Last-Modified");
- evhttp_remove_header(output_headers, "ETag");
+ httpd_header_remove(hreq->out_headers, "Cache-Control");
+ httpd_header_remove(hreq->out_headers, "Last-Modified");
+ httpd_header_remove(hreq->out_headers, "ETag");
// Tell clients that they are not allowed to cache this response
- evhttp_add_header(output_headers, "Cache-Control", "no-store");
+ httpd_header_add(hreq->out_headers, "Cache-Control", "no-store");
}
static void
-serve_file(struct evhttp_request *req, const char *uri)
+serve_file(struct httpd_request *hreq)
{
char *ext;
char path[PATH_MAX];
char deref[PATH_MAX];
char *ctype;
- struct evbuffer *evbuf;
- struct evkeyvalq *output_headers;
struct stat sb;
int fd;
int i;
@@ -373,16 +436,15 @@ serve_file(struct evhttp_request *req, const char *uri)
int ret;
/* Check authentication */
- if (!httpd_admin_check_auth(req))
+ if (!httpd_admin_check_auth(hreq))
return;
- ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, uri);
+ ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->path);
if ((ret < 0) || (ret >= sizeof(path)))
{
- DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri);
-
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
+ DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", hreq->uri);
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -390,7 +452,7 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -398,8 +460,7 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
-
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -408,8 +469,7 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_WARN, L_HTTPD, "Could not lstat() %s: %s\n", deref, strerror(errno));
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
-
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -422,7 +482,7 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -430,7 +490,7 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -439,7 +499,7 @@ serve_file(struct evhttp_request *req, const char *uri)
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno));
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
}
@@ -448,23 +508,14 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_WARN, L_HTTPD, "Access to file outside the web root dir forbidden: %s\n", deref);
- httpd_send_error(req, 403, "Forbidden");
+ httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden");
return;
}
- if (httpd_request_not_modified_since(req, sb.st_mtime))
+ if (httpd_request_not_modified_since(hreq, sb.st_mtime))
{
- httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
- return;
- }
-
- evbuf = evbuffer_new();
- if (!evbuf)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n");
-
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
+ httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, HTTPD_SEND_NO_GZIP);
return;
}
@@ -473,12 +524,11 @@ serve_file(struct evhttp_request *req, const char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", deref, strerror(errno));
- httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
- evbuffer_free(evbuf);
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
- ret = evbuffer_expand(evbuf, sb.st_size);
+ ret = evbuffer_expand(hreq->out_body, sb.st_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Out of memory for htdocs-file\n");
@@ -486,7 +536,7 @@ serve_file(struct evhttp_request *req, const char *uri)
}
while ((ret = read(fd, buf, sizeof(buf))) > 0)
- evbuffer_add(evbuf, buf, ret);
+ evbuffer_add(hreq->out_body, buf, ret);
if (ret < 0)
{
@@ -508,56 +558,57 @@ serve_file(struct evhttp_request *req, const char *uri)
}
}
- output_headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(output_headers, "Content-Type", ctype);
+ httpd_header_add(hreq->out_headers, "Content-Type", ctype);
- httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
- evbuffer_free(evbuf);
close(fd);
return;
out_fail:
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
- evbuffer_free(evbuf);
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal error");
close(fd);
}
/* ---------------------------- STREAM HANDLING ----------------------------- */
+// This will triggered in a httpd thread, but since the reading may be in a
+// worker thread we just want to trigger the read loop
static void
-stream_end(struct stream_ctx *st, int failed)
+stream_chunk_resched_cb(httpd_connection *conn, void *arg)
{
- struct evhttp_connection *evcon;
+ struct stream_ctx *st = arg;
- evcon = evhttp_request_get_connection(st->req);
+ // TODO not thread safe if st was freed in worker thread, but maybe not possible?
+ event_active(st->ev, 0, 0);
+}
- if (evcon)
- evhttp_connection_set_closecb(evcon, NULL, NULL);
+static void
+stream_free(struct stream_ctx *st)
+{
+ if (!st)
+ return;
- if (!failed)
- evhttp_send_reply_end(st->req);
-
- evbuffer_free(st->evbuf);
- event_free(st->ev);
-
- if (st->xcode)
- transcode_cleanup(&st->xcode);
- else
- {
- free(st->buf);
- close(st->fd);
- }
-
-#ifdef HAVE_LIBEVENT2_OLD
- if (g_st == st)
- g_st = NULL;
-#endif
+ if (st->ev)
+ event_free(st->ev);
+ if (st->fd >= 0)
+ close(st->fd);
+ transcode_cleanup(&st->xcode);
free(st);
}
+static void
+stream_end(struct stream_ctx *st)
+{
+ DPRINTF(E_DBG, L_HTTPD, "Ending stream %d\n", st->id);
+
+ httpd_send_reply_end(st->hreq);
+
+ stream_free(st);
+}
+
static void
stream_end_register(struct stream_ctx *st)
{
@@ -573,45 +624,134 @@ stream_end_register(struct stream_ctx *st)
}
}
-static void
-stream_chunk_resched_cb(struct evhttp_connection *evcon, void *arg)
+static struct stream_ctx *
+stream_new(struct media_file_info *mfi, struct httpd_request *hreq, event_callback_fn stream_cb)
{
struct stream_ctx *st;
- struct timeval tv;
+
+ CHECK_NULL(L_HTTPD, st = calloc(1, sizeof(struct stream_ctx)));
+ st->fd = -1;
+
+ st->ev = event_new(hreq->evbase, -1, EV_PERSIST, stream_cb, st);
+ if (!st->ev)
+ {
+ DPRINTF(E_LOG, L_HTTPD, "Could not create event for streaming\n");
+
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
+ goto error;
+ }
+
+ event_active(st->ev, 0, 0);
+
+ st->id = mfi->id;
+ st->hreq = hreq;
+ return st;
+
+ error:
+ stream_free(st);
+ return NULL;
+}
+
+static struct stream_ctx *
+stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
+{
+ struct stream_ctx *st;
+ struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 };
+
+ st = stream_new(mfi, hreq, stream_cb);
+ if (!st)
+ {
+ goto error;
+ }
+
+ st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
+ if (!st->xcode)
+ {
+ DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
+
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
+ goto error;
+ }
+
+ st->stream_size = st->size - offset;
+ if (end_offset > 0)
+ st->stream_size -= (st->size - end_offset);
+
+ st->start_offset = offset;
+
+ return st;
+
+ error:
+ stream_free(st);
+ return NULL;
+}
+
+static struct stream_ctx *
+stream_new_raw(struct media_file_info *mfi, struct httpd_request *hreq, int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
+{
+ struct stream_ctx *st;
+ struct stat sb;
+ off_t pos;
int ret;
- st = (struct stream_ctx *)arg;
+ st = stream_new(mfi, hreq, stream_cb);
+ if (!st)
+ {
+ goto error;
+ }
- evutil_timerclear(&tv);
- ret = event_add(st->ev, &tv);
+ st->fd = open(mfi->path, O_RDONLY);
+ if (st->fd < 0)
+ {
+ DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));
+
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
+ goto error;
+ }
+
+ ret = stat(mfi->path, &sb);
if (ret < 0)
{
- DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n");
+ DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));
- stream_end(st, 0);
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
+ goto error;
}
-}
-#ifdef HAVE_LIBEVENT2_OLD
-static void
-stream_chunk_resched_cb_wrapper(struct bufferevent *bufev, void *arg)
-{
- if (g_st)
- stream_chunk_resched_cb(NULL, g_st);
+ st->size = sb.st_size;
+
+ st->stream_size = st->size - offset;
+ if (end_offset > 0)
+ st->stream_size -= (st->size - end_offset);
+
+ st->start_offset = offset;
+ st->offset = offset;
+ st->end_offset = end_offset;
+
+ pos = lseek(st->fd, offset, SEEK_SET);
+ if (pos == (off_t) -1)
+ {
+ DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));
+
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
+ goto error;
+ }
+
+ return st;
+
+ error:
+ stream_free(st);
+ return NULL;
}
-#endif
static void
stream_chunk_xcode_cb(int fd, short event, void *arg)
{
- struct stream_ctx *st;
- struct timeval tv;
+ struct stream_ctx *st = arg;
int xcoded;
int ret;
- st = (struct stream_ctx *)arg;
-
- xcoded = transcode(st->evbuf, NULL, st->xcode, STREAM_CHUNK_SIZE);
+ xcoded = transcode(st->hreq->out_body, NULL, st->xcode, STREAM_CHUNK_SIZE);
if (xcoded <= 0)
{
if (xcoded == 0)
@@ -619,77 +759,54 @@ stream_chunk_xcode_cb(int fd, short event, void *arg)
else
DPRINTF(E_LOG, L_HTTPD, "Transcoding error, file id %d\n", st->id);
- stream_end(st, 0);
+ stream_end(st);
return;
}
DPRINTF(E_DBG, L_HTTPD, "Got %d bytes from transcode; streaming file id %d\n", xcoded, st->id);
- /* Consume transcoded data until we meet start_offset */
+ // Consume transcoded data until we meet start_offset
if (st->start_offset > st->offset)
{
ret = st->start_offset - st->offset;
if (ret < xcoded)
{
- evbuffer_drain(st->evbuf, ret);
+ evbuffer_drain(st->hreq->out_body, ret);
st->offset += ret;
ret = xcoded - ret;
}
else
{
- evbuffer_drain(st->evbuf, xcoded);
+ evbuffer_drain(st->hreq->out_body, xcoded);
st->offset += xcoded;
- goto consume;
+ // Reschedule immediately - consume up to start_offset
+ event_active(st->ev, 0, 0);
+ return;
}
}
else
ret = xcoded;
-#ifdef HAVE_LIBEVENT2_OLD
- evhttp_send_reply_chunk(st->req, st->evbuf);
-
- struct evhttp_connection *evcon = evhttp_request_get_connection(st->req);
- struct bufferevent *bufev = evhttp_connection_get_bufferevent(evcon);
-
- g_st = st; // Can't pass st to callback so use global - limits libevent 2.0 to a single stream
- bufev->writecb = stream_chunk_resched_cb_wrapper;
-#else
- evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
-#endif
+ httpd_send_reply_chunk(st->hreq, stream_chunk_resched_cb, st);
st->offset += ret;
stream_end_register(st);
-
- return;
-
- consume: /* reschedule immediately - consume up to start_offset */
- evutil_timerclear(&tv);
- ret = event_add(st->ev, &tv);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming (xcode)\n");
-
- stream_end(st, 0);
- return;
- }
}
static void
stream_chunk_raw_cb(int fd, short event, void *arg)
{
- struct stream_ctx *st;
+ struct stream_ctx *st = arg;
size_t chunk_size;
int ret;
- st = (struct stream_ctx *)arg;
-
if (st->end_offset && (st->offset > st->end_offset))
{
- stream_end(st, 0);
+ stream_end(st);
return;
}
@@ -698,7 +815,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
else
chunk_size = STREAM_CHUNK_SIZE;
- ret = read(st->fd, st->buf, chunk_size);
+ ret = evbuffer_read(st->hreq->out_body, st->fd, chunk_size);
if (ret <= 0)
{
if (ret == 0)
@@ -706,25 +823,13 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
else
DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id);
- stream_end(st, 0);
+ stream_end(st);
return;
}
DPRINTF(E_DBG, L_HTTPD, "Read %d bytes; streaming file id %d\n", ret, st->id);
- evbuffer_add(st->evbuf, st->buf, ret);
-
-#ifdef HAVE_LIBEVENT2_OLD
- evhttp_send_reply_chunk(st->req, st->evbuf);
-
- struct evhttp_connection *evcon = evhttp_request_get_connection(st->req);
- struct bufferevent *bufev = evhttp_connection_get_bufferevent(evcon);
-
- g_st = st; // Can't pass st to callback so use global - limits libevent 2.0 to a single stream
- bufev->writecb = stream_chunk_resched_cb_wrapper;
-#else
- evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
-#endif
+ httpd_send_reply_chunk(st->hreq, stream_chunk_resched_cb, st);
st->offset += ret;
@@ -732,341 +837,91 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
}
static void
-stream_fail_cb(struct evhttp_connection *evcon, void *arg)
+stream_fail_cb(void *arg)
{
- struct stream_ctx *st;
+ struct stream_ctx *st = arg;
- st = (struct stream_ctx *)arg;
-
- DPRINTF(E_WARN, L_HTTPD, "Connection failed; stopping streaming of file ID %d\n", st->id);
-
- /* Stop streaming */
- event_del(st->ev);
-
- stream_end(st, 1);
+ stream_free(st);
}
-/* ---------------------------- MAIN HTTPD THREAD --------------------------- */
+/* ---------------------------- REQUEST CALLBACKS --------------------------- */
-static void *
-httpd(void *arg)
+// Worker thread, invoked by request_cb() below
+static void
+request_async_cb(void *arg)
{
- int ret;
+ struct httpd_request *hreq = *(struct httpd_request **)arg;
- ret = db_perthread_init();
- if (ret < 0)
+#ifdef HAVE_SYSCALL
+ DPRINTF(E_DBG, hreq->module->logdomain, "%s request '%s' in worker thread %ld\n", hreq->module->name, hreq->uri, syscall(SYS_gettid));
+#endif
+
+ // Some handlers require an evbase to schedule events
+ hreq->evbase = worker_evbase_get();
+ hreq->module->request(hreq);
+}
+
+// httpd thread
+static void
+request_cb(struct httpd_request *hreq, void *arg)
+{
+ if (is_cors_preflight(hreq, httpd_allow_origin))
{
- DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n");
-
- pthread_exit(NULL);
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
+ return;
}
-
- event_base_dispatch(evbase_httpd);
-
- if (!httpd_exit)
- DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n");
-
- db_perthread_deinit();
-
- pthread_exit(NULL);
-}
-
-static void
-exit_cb(int fd, short event, void *arg)
-{
- event_base_loopbreak(evbase_httpd);
-
- httpd_exit = 1;
-}
-
-static void
-httpd_gen_cb(struct evhttp_request *req, void *arg)
-{
- struct evkeyvalq *input_headers;
- struct evkeyvalq *output_headers;
- struct httpd_uri_parsed *parsed;
- const char *uri;
-
- // Clear the proxy request flag set by evhttp if the request URI was absolute.
- // It has side-effects on Connection: keep-alive
- req->flags &= ~EVHTTP_PROXY_REQUEST;
-
- // Did we get a CORS preflight request?
- input_headers = evhttp_request_get_input_headers(req);
- if ( input_headers && allow_origin &&
- (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) &&
- evhttp_find_header(input_headers, "Origin") &&
- evhttp_find_header(input_headers, "Access-Control-Request-Method") )
+ else if (!hreq->uri || !hreq->uri_parsed)
{
- output_headers = evhttp_request_get_output_headers(req);
-
- evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
- evhttp_add_header(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
- evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization");
-
- // In this case there is no reason to go through httpd_send_reply
- evhttp_send_reply(req, HTTP_OK, "OK", NULL);
+ DPRINTF(E_WARN, L_HTTPD, "Invalid URI in request: '%s'\n", hreq->uri);
+ httpd_redirect_to(hreq, "/");
+ return;
+ }
+ else if (!hreq->path)
+ {
+ DPRINTF(E_WARN, L_HTTPD, "Invalid path in request: '%s'\n", hreq->uri);
+ httpd_redirect_to(hreq, "/");
return;
}
- uri = evhttp_request_get_uri(req);
- if (!uri)
+ httpd_request_handler_set(hreq);
+ if (hreq->module && hreq->is_async)
{
- DPRINTF(E_WARN, L_HTTPD, "No URI in request\n");
- httpd_redirect_to(req, "/");
- return;
+ worker_execute(request_async_cb, &hreq, sizeof(struct httpd_request *), 0);
+ }
+ else if (hreq->module)
+ {
+ DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s'\n", hreq->module->name, hreq->uri);
+ hreq->evbase = httpd_backend_evbase_get(hreq->backend);
+ hreq->module->request(hreq);
+ }
+ else
+ {
+ // Serve web interface files
+ DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", hreq->uri);
+ serve_file(hreq);
}
- parsed = httpd_uri_parse(uri);
- if (!parsed || !parsed->path)
- {
- httpd_redirect_to(req, "/");
- goto out;
- }
-
- if (strcmp(parsed->path, "/") == 0)
- {
- goto serve_file;
- }
-
- /* Dispatch protocol-specific handlers */
- if (dacp_is_request(parsed->path))
- {
- dacp_request(req, parsed);
- goto out;
- }
- else if (daap_is_request(parsed->path))
- {
- daap_request(req, parsed);
- goto out;
- }
- else if (jsonapi_is_request(parsed->path))
- {
- jsonapi_request(req, parsed);
- goto out;
- }
- else if (artworkapi_is_request(parsed->path))
- {
- artworkapi_request(req, parsed);
- goto out;
- }
- else if (streaming_is_request(parsed->path))
- {
- streaming_request(req, parsed);
- goto out;
- }
- else if (oauth_is_request(parsed->path))
- {
- oauth_request(req, parsed);
- goto out;
- }
- else if (rsp_is_request(parsed->path))
- {
- rsp_request(req, parsed);
- goto out;
- }
-
- DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri);
-
- /* Serve web interface files */
- serve_file:
- serve_file(req, parsed->path);
-
- out:
- httpd_uri_free(parsed);
+ // Don't touch hreq here, if async it has been passed to a worker thread
}
/* ------------------------------- HTTPD API -------------------------------- */
void
-httpd_uri_free(struct httpd_uri_parsed *parsed)
+httpd_stream_file(struct httpd_request *hreq, int id)
{
- int i;
-
- if (!parsed)
- return;
-
- free(parsed->uri_decoded);
- free(parsed->path);
- for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
- {
- free(parsed->path_parts[i]);
- }
-
- evhttp_clear_headers(&(parsed->ev_query));
-
- if (parsed->ev_uri)
- evhttp_uri_free(parsed->ev_uri);
-
- free(parsed);
-}
-
-struct httpd_uri_parsed *
-httpd_uri_parse(const char *uri)
-{
- struct httpd_uri_parsed *parsed;
- char *path = NULL;
- const char *query;
- char *path_part;
- char *ptr;
- int i;
- int ret;
-
- CHECK_NULL(L_HTTPD, parsed = calloc(1, sizeof(struct httpd_uri_parsed)));
-
- parsed->uri = uri;
-
- parsed->ev_uri = evhttp_uri_parse_with_flags(parsed->uri, EVHTTP_URI_NONCONFORMANT);
- if (!parsed->ev_uri)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not parse request: '%s'\n", parsed->uri);
- goto error;
- }
-
- parsed->uri_decoded = evhttp_uridecode(parsed->uri, 0, NULL);
- if (!parsed->uri_decoded)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not URI decode request: '%s'\n", parsed->uri);
- goto error;
- }
-
- query = evhttp_uri_get_query(parsed->ev_uri);
- if (query && strchr(query, '='))
- {
- ret = evhttp_parse_query_str(query, &(parsed->ev_query));
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in request: '%s'\n", query, parsed->uri);
- goto error;
- }
- }
-
- path = strdup(evhttp_uri_get_path(parsed->ev_uri));
- if (!path)
- {
- DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri);
- return parsed;
- }
-
- parsed->path = evhttp_uridecode(path, 0, NULL);
- if (!parsed->path)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not URI decode path: '%s'\n", path);
- goto error;
- }
-
- path_part = strtok_r(path, "/", &ptr);
- for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
- {
- parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
- path_part = strtok_r(NULL, "/", &ptr);
- }
-
- if (path_part)
- {
- // If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
- DPRINTF(E_LOG, L_HTTPD, "URI path has too many components (%d): '%s'\n", i, parsed->path);
- goto error;
- }
-
- free(path);
-
- return parsed;
-
- error:
- free(path);
- httpd_uri_free(parsed);
- return NULL;
-}
-
-struct httpd_request *
-httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map)
-{
- struct httpd_request *hreq;
- struct evhttp_connection *evcon;
- struct evkeyvalq *headers;
- int req_method;
- int i;
- int ret;
-
- CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request)));
-
- // Note req is allowed to be NULL
- hreq->req = req;
- hreq->uri_parsed = uri_parsed;
- hreq->query = &(uri_parsed->ev_query);
- req_method = 0;
-
- if (req)
- {
- headers = evhttp_request_get_input_headers(req);
- hreq->user_agent = evhttp_find_header(headers, "User-Agent");
-
- evcon = evhttp_request_get_connection(req);
- if (evcon)
- httpd_peer_get(&hreq->peer_address, &hreq->peer_port, evcon);
- else
- DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
-
- req_method = evhttp_request_get_command(req);
- }
-
- if (user_agent)
- hreq->user_agent = user_agent;
-
- // Find a handler for the path
- for (i = 0; uri_map[i].handler; i++)
- {
- // Check if handler supports the current http request method
- if (uri_map[i].method && req_method && !(req_method & uri_map[i].method))
- continue;
-
- ret = regexec(&uri_map[i].preg, uri_parsed->path, 0, NULL, 0);
- if (ret == 0)
- {
- hreq->handler = uri_map[i].handler;
- return hreq; // Success
- }
- }
-
- // Handler not found, that's an error
- free(hreq);
-
- return NULL;
-}
-
-/* Thread: httpd */
-void
-httpd_stream_file(struct evhttp_request *req, int id)
-{
- struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 };
- struct media_file_info *mfi;
- struct stream_ctx *st;
- void (*stream_cb)(int fd, short event, void *arg);
- struct stat sb;
- struct timeval tv;
- struct evhttp_connection *evcon;
- struct evkeyvalq *input_headers;
- struct evkeyvalq *output_headers;
+ struct media_file_info *mfi = NULL;
+ struct stream_ctx *st = NULL;
const char *param;
const char *param_end;
- const char *ua;
- const char *client_codecs;
char buf[64];
- int64_t offset;
- int64_t end_offset;
- off_t pos;
+ int64_t offset = 0;
+ int64_t end_offset = 0;
int transcode;
int ret;
- offset = 0;
- end_offset = 0;
-
- input_headers = evhttp_request_get_input_headers(req);
-
- param = evhttp_find_header(input_headers, "Range");
+ param = httpd_header_find(hreq->in_headers, "Range");
if (param)
{
DPRINTF(E_DBG, L_HTTPD, "Found Range header: %s\n", param);
@@ -1105,198 +960,86 @@ httpd_stream_file(struct evhttp_request *req, int id)
{
DPRINTF(E_LOG, L_HTTPD, "Item %d not found\n", id);
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
- return;
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
+ goto error;
}
if (mfi->data_kind != DATA_KIND_FILE)
{
DPRINTF(E_LOG, L_HTTPD, "Could not serve '%s' to client, not a file\n", mfi->path);
- evhttp_send_error(req, 500, "Cannot stream non-file content");
- goto out_free_mfi;
+ httpd_send_error(hreq, HTTP_INTERNAL, "Cannot stream non-file content");
+ goto error;
}
- st = (struct stream_ctx *)malloc(sizeof(struct stream_ctx));
- if (!st)
- {
- DPRINTF(E_LOG, L_HTTPD, "Out of memory for struct stream_ctx\n");
-
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
- goto out_free_mfi;
- }
- memset(st, 0, sizeof(struct stream_ctx));
- st->fd = -1;
-
- ua = evhttp_find_header(input_headers, "User-Agent");
- client_codecs = evhttp_find_header(input_headers, "Accept-Codecs");
-
- transcode = transcode_needed(ua, client_codecs, mfi->codectype);
-
- output_headers = evhttp_request_get_output_headers(req);
+ param = httpd_header_find(hreq->in_headers, "Accept-Codecs");
+ transcode = transcode_needed(hreq->user_agent, param, mfi->codectype);
if (transcode)
{
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
- stream_cb = stream_chunk_xcode_cb;
+ st = stream_new_transcode(mfi, hreq, offset, end_offset, stream_chunk_xcode_cb);
+ if (!st)
+ goto error;
- st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
- if (!st->xcode)
- {
- DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
-
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
- goto out_free_st;
- }
-
- if (!evhttp_find_header(output_headers, "Content-Type"))
- evhttp_add_header(output_headers, "Content-Type", "audio/wav");
+ if (!httpd_header_find(hreq->out_headers, "Content-Type"))
+ httpd_header_add(hreq->out_headers, "Content-Type", "audio/wav");
}
else
{
- /* Stream the raw file */
DPRINTF(E_INFO, L_HTTPD, "Preparing to stream %s\n", mfi->path);
- st->buf = (uint8_t *)malloc(STREAM_CHUNK_SIZE);
- if (!st->buf)
- {
- DPRINTF(E_LOG, L_HTTPD, "Out of memory for raw streaming buffer\n");
+ st = stream_new_raw(mfi, hreq, offset, end_offset, stream_chunk_raw_cb);
+ if (!st)
+ goto error;
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
- goto out_free_st;
- }
-
- stream_cb = stream_chunk_raw_cb;
-
- st->fd = open(mfi->path, O_RDONLY);
- if (st->fd < 0)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));
-
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
-
- goto out_cleanup;
- }
-
- ret = stat(mfi->path, &sb);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));
-
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
-
- goto out_cleanup;
- }
- st->size = sb.st_size;
-
- pos = lseek(st->fd, offset, SEEK_SET);
- if (pos == (off_t) -1)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));
-
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
-
- goto out_cleanup;
- }
- st->offset = offset;
- st->end_offset = end_offset;
-
- /* Content-Type for video files is different than for audio files
- * and overrides whatever may have been set previously, like
- * application/x-dmap-tagged when we're speaking DAAP.
- */
+ // Content-Type for video files is different than for audio files and
+ // overrides whatever may have been set previously, like
+ // application/x-dmap-tagged when we're speaking DAAP.
if (mfi->has_video)
{
- /* Front Row and others expect video/ */
+ // Front Row and others expect video/
ret = snprintf(buf, sizeof(buf), "video/%s", mfi->type);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
else
{
- evhttp_remove_header(output_headers, "Content-Type");
- evhttp_add_header(output_headers, "Content-Type", buf);
+ httpd_header_remove(hreq->out_headers, "Content-Type");
+ httpd_header_add(hreq->out_headers, "Content-Type", buf);
}
}
- /* If no Content-Type has been set and we're streaming audio, add a proper
- * Content-Type for the file we're streaming. Remember DAAP streams audio
- * with application/x-dmap-tagged as the Content-Type (ugh!).
- */
- else if (!evhttp_find_header(output_headers, "Content-Type") && mfi->type)
+ // If no Content-Type has been set and we're streaming audio, add a proper
+ // Content-Type for the file we're streaming. Remember DAAP streams audio
+ // with application/x-dmap-tagged as the Content-Type (ugh!).
+ else if (!httpd_header_find(hreq->out_headers, "Content-Type") && mfi->type)
{
ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
else
- evhttp_add_header(output_headers, "Content-Type", buf);
+ httpd_header_add(hreq->out_headers, "Content-Type", buf);
}
}
- st->evbuf = evbuffer_new();
- if (!st->evbuf)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not allocate an evbuffer for streaming\n");
-
- evhttp_clear_headers(output_headers);
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
- goto out_cleanup;
- }
-
- ret = evbuffer_expand(st->evbuf, STREAM_CHUNK_SIZE);
- if (ret != 0)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not expand evbuffer for streaming\n");
-
- evhttp_clear_headers(output_headers);
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
- goto out_cleanup;
- }
-
- st->ev = event_new(evbase_httpd, -1, EV_TIMEOUT, stream_cb, st);
- evutil_timerclear(&tv);
- if (!st->ev || (event_add(st->ev, &tv) < 0))
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");
-
- evhttp_clear_headers(output_headers);
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
- goto out_cleanup;
- }
-
- st->id = mfi->id;
- st->start_offset = offset;
- st->stream_size = st->size;
- st->req = req;
-
if ((offset == 0) && (end_offset == 0))
{
- /* If we are not decoding, send the Content-Length. We don't do
- * that if we are decoding because we can only guesstimate the
- * size in this case and the error margin is unknown and variable.
- */
+ // If we are not decoding, send the Content-Length. We don't do that if we
+ // are decoding because we can only guesstimate the size in this case and
+ // the error margin is unknown and variable.
if (!transcode)
{
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
else
- evhttp_add_header(output_headers, "Content-Length", buf);
+ httpd_header_add(hreq->out_headers, "Content-Length", buf);
}
- evhttp_send_reply_start(req, HTTP_OK, "OK");
+ httpd_send_reply_start(hreq, HTTP_OK, "OK");
}
else
{
- if (offset > 0)
- st->stream_size -= offset;
- if (end_offset > 0)
- st->stream_size -= (st->size - end_offset);
-
DPRINTF(E_DBG, L_HTTPD, "Stream request with range %" PRIi64 "-%" PRIi64 "\n", offset, end_offset);
ret = snprintf(buf, sizeof(buf), "bytes %" PRIi64 "-%" PRIi64 "/%" PRIi64,
@@ -1304,21 +1047,21 @@ httpd_stream_file(struct evhttp_request *req, int id)
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Range too large for buffer, dropping\n");
else
- evhttp_add_header(output_headers, "Content-Range", buf);
+ httpd_header_add(hreq->out_headers, "Content-Range", buf);
ret = snprintf(buf, sizeof(buf), "%" PRIi64, ((end_offset) ? end_offset + 1 : (int64_t)st->size) - offset);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
else
- evhttp_add_header(output_headers, "Content-Length", buf);
+ httpd_header_add(hreq->out_headers, "Content-Length", buf);
- evhttp_send_reply_start(req, 206, "Partial Content");
+ httpd_send_reply_start(hreq, 206, "Partial Content");
}
#ifdef HAVE_POSIX_FADVISE
if (!transcode)
{
- /* Hint the OS */
+ // Hint the OS
if ( (ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED)) != 0 ||
(ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_SEQUENTIAL)) != 0 ||
(ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_NOREUSE)) != 0 )
@@ -1326,28 +1069,15 @@ httpd_stream_file(struct evhttp_request *req, int id)
}
#endif
- evcon = evhttp_request_get_connection(req);
-
- evhttp_connection_set_closecb(evcon, stream_fail_cb, st);
+ httpd_request_close_cb_set(hreq, stream_fail_cb, st);
DPRINTF(E_INFO, L_HTTPD, "Kicking off streaming for %s\n", mfi->path);
free_mfi(mfi, 0);
-
return;
- out_cleanup:
- if (st->evbuf)
- evbuffer_free(st->evbuf);
- if (st->xcode)
- transcode_cleanup(&st->xcode);
- if (st->buf)
- free(st->buf);
- if (st->fd > 0)
- close(st->fd);
- out_free_st:
- free(st);
- out_free_mfi:
+ error:
+ stream_free(st);
free_mfi(mfi, 0);
}
@@ -1418,116 +1148,106 @@ httpd_gzip_deflate(struct evbuffer *in)
return NULL;
}
+
+// The httpd_send functions below can be called from a worker thread (with
+// hreq->is_async) or directly from the httpd thread. In the former case, they
+// will command sending from the httpd thread, since it is not safe to access
+// the backend (evhttp) from a worker thread. hreq will be freed (again,
+// possibly async) if the type is either _COMPLETE or _END.
+
void
-httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags)
+httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, enum httpd_send_flags flags)
{
struct evbuffer *gzbuf;
- struct evkeyvalq *input_headers;
- struct evkeyvalq *output_headers;
+ struct evbuffer *save;
const char *param;
int do_gzip;
- if (!req)
+ if (!hreq->backend)
return;
- input_headers = evhttp_request_get_input_headers(req);
- output_headers = evhttp_request_get_output_headers(req);
-
do_gzip = ( (!(flags & HTTPD_SEND_NO_GZIP)) &&
- evbuf && (evbuffer_get_length(evbuf) > 512) &&
- (param = evhttp_find_header(input_headers, "Accept-Encoding")) &&
+ (evbuffer_get_length(hreq->out_body) > 512) &&
+ (param = httpd_header_find(hreq->in_headers, "Accept-Encoding")) &&
(strstr(param, "gzip") || strstr(param, "*"))
);
- if (allow_origin)
- evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
+ cors_headers_add(hreq, httpd_allow_origin);
- if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf)))
+ if (do_gzip && (gzbuf = httpd_gzip_deflate(hreq->out_body)))
{
DPRINTF(E_DBG, L_HTTPD, "Gzipping response\n");
- evhttp_add_header(output_headers, "Content-Encoding", "gzip");
- evhttp_send_reply(req, code, reason, gzbuf);
- evbuffer_free(gzbuf);
+ httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip");
+ save = hreq->out_body;
+ hreq->out_body = gzbuf;
+ evbuffer_free(save);
+ }
- // Drain original buffer, as would be after evhttp_send_reply()
- evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
- }
- else
- {
- evhttp_send_reply(req, code, reason, evbuf);
- }
+ httpd_send(hreq, HTTPD_REPLY_COMPLETE, code, reason, NULL, NULL);
+}
+
+void
+httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason)
+{
+ cors_headers_add(hreq, httpd_allow_origin);
+
+ httpd_send(hreq, HTTPD_REPLY_START, code, reason, NULL, NULL);
+}
+
+void
+httpd_send_reply_chunk(struct httpd_request *hreq, httpd_connection_chunkcb cb, void *arg)
+{
+ httpd_send(hreq, HTTPD_REPLY_CHUNK, 0, NULL, cb, arg);
+}
+
+void
+httpd_send_reply_end(struct httpd_request *hreq)
+{
+ httpd_send(hreq, HTTPD_REPLY_END, 0, NULL, NULL, NULL);
}
// This is a modified version of evhttp_send_error (credit libevent)
void
-httpd_send_error(struct evhttp_request* req, int error, const char* reason)
+httpd_send_error(struct httpd_request *hreq, int error, const char *reason)
{
- struct evkeyvalq *output_headers;
- struct evbuffer *evbuf;
+ evbuffer_drain(hreq->out_body, -1);
+ httpd_headers_clear(hreq->out_headers);
- if (!allow_origin)
- {
- evhttp_send_error(req, error, reason);
- return;
- }
+ cors_headers_add(hreq, httpd_allow_origin);
- output_headers = evhttp_request_get_output_headers(req);
+ httpd_header_add(hreq->out_headers, "Content-Type", "text/html");
+ httpd_header_add(hreq->out_headers, "Connection", "close");
- evhttp_clear_headers(output_headers);
+ evbuffer_add_printf(hreq->out_body, ERR_PAGE, error, reason, reason);
- evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
- evhttp_add_header(output_headers, "Content-Type", "text/html");
- evhttp_add_header(output_headers, "Connection", "close");
-
- evbuf = evbuffer_new();
- if (!evbuf)
- DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for error page\n");
- else
- evbuffer_add_printf(evbuf, ERR_PAGE, error, reason, reason);
-
- evhttp_send_reply(req, error, reason, evbuf);
-
- if (evbuf)
- evbuffer_free(evbuf);
+ httpd_send(hreq, HTTPD_REPLY_COMPLETE, error, reason, NULL, NULL);
}
bool
-httpd_admin_check_auth(struct evhttp_request *req)
+httpd_admin_check_auth(struct httpd_request *hreq)
{
- struct evhttp_connection *evcon;
- const char *addr;
- uint16_t port;
const char *passwd;
int ret;
- evcon = evhttp_request_get_connection(req);
- if (!evcon)
- {
- DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
- return false;
- }
-
- httpd_peer_get(&addr, &port, evcon);
-
- if (net_peer_address_is_trusted(addr))
+ if (net_peer_address_is_trusted(hreq->peer_address))
return true;
passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password");
if (!passwd)
{
- DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: No password set in the config\n", evhttp_request_get_uri(req));
+ DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: No password set in the config\n", hreq->uri);
- httpd_send_error(req, 403, "Forbidden");
+ httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden");
return false;
}
DPRINTF(E_DBG, L_HTTPD, "Checking web interface authentication\n");
- ret = httpd_basic_auth(req, "admin", passwd, PACKAGE " web interface");
+ ret = httpd_basic_auth(hreq, "admin", passwd, PACKAGE " web interface");
if (ret != 0)
{
- DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: Incorrect password\n", evhttp_request_get_uri(req));
+ DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: Incorrect password\n", hreq->uri);
// httpd_basic_auth has sent a reply
return false;
@@ -1539,18 +1259,15 @@ httpd_admin_check_auth(struct evhttp_request *req)
}
int
-httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm)
+httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm)
{
- struct evbuffer *evbuf;
- struct evkeyvalq *headers;
char header[256];
const char *auth;
char *authuser;
char *authpwd;
int ret;
- headers = evhttp_request_get_input_headers(req);
- auth = evhttp_find_header(headers, "Authorization");
+ auth = httpd_header_find(hreq->in_headers, "Authorization");
if (!auth)
{
DPRINTF(E_DBG, L_HTTPD, "No Authorization header\n");
@@ -1614,37 +1331,59 @@ httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passw
ret = snprintf(header, sizeof(header), "Basic realm=\"%s\"", realm);
if ((ret < 0) || (ret >= sizeof(header)))
{
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
- evbuf = evbuffer_new();
- if (!evbuf)
- {
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
- return -1;
- }
+ httpd_header_add(hreq->out_headers, "WWW-Authenticate", header);
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "WWW-Authenticate", header);
+ evbuffer_add_printf(hreq->out_body, ERR_PAGE, HTTP_UNAUTHORIZED, "Unauthorized", "Authorization required");
- evbuffer_add(evbuf, http_reply_401, strlen(http_reply_401));
-
- httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP);
-
- evbuffer_free(evbuf);
+ httpd_send_reply(hreq, HTTP_UNAUTHORIZED, "Unauthorized", HTTPD_SEND_NO_GZIP);
return -1;
}
-void
-httpd_peer_get(const char **address, ev_uint16_t *port, struct evhttp_connection *evcon)
+static int
+bind_test(short unsigned port)
{
-#ifdef HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR
- evhttp_connection_get_peer(evcon, address, port);
-#else
- evhttp_connection_get_peer(evcon, (char **)address, port);
-#endif
+ int fd;
+
+ fd = net_bind(&port, SOCK_STREAM, "httpd init");
+ if (fd < 0)
+ return -1;
+
+ close(fd);
+ return 0;
+}
+
+static void
+thread_init_cb(struct evthr *thr, void *shared)
+{
+ struct event_base *evbase;
+ httpd_server *server;
+
+ thread_setname(pthread_self(), "httpd");
+
+ CHECK_ERR(L_HTTPD, db_perthread_init());
+ CHECK_NULL(L_HTTPD, evbase = evthr_get_base(thr));
+ CHECK_NULL(L_HTTPD, server = httpd_server_new(evbase, httpd_port, request_cb, NULL));
+
+ // For CORS headers
+ httpd_server_allow_origin_set(server, httpd_allow_origin);
+
+ evthr_set_aux(thr, server);
+}
+
+static void
+thread_exit_cb(struct evthr *thr, void *shared)
+{
+ httpd_server *server;
+
+ server = evthr_get_aux(thr);
+ httpd_server_free(server);
+
+ db_perthread_deinit();
}
/* Thread: main */
@@ -1654,83 +1393,45 @@ httpd_init(const char *webroot)
struct stat sb;
int ret;
- httpd_exit = 0;
-
DPRINTF(E_DBG, L_HTTPD, "Starting web server with root directory '%s'\n", webroot);
ret = stat(webroot, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not stat() web root directory '%s': %s\n", webroot, strerror(errno));
-
return -1;
}
if (!S_ISDIR(sb.st_mode))
{
DPRINTF(E_LOG, L_HTTPD, "Web root directory '%s' is not a directory\n", webroot);
-
return -1;
}
if (!realpath(webroot, webroot_directory))
{
DPRINTF(E_LOG, L_HTTPD, "Web root directory '%s' could not be dereferenced: %s\n", webroot, strerror(errno));
-
return -1;
}
- evbase_httpd = event_base_new();
- if (!evbase_httpd)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not create an event base\n");
+ // Read config
+ httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
+ httpd_allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
+ if (strlen(httpd_allow_origin) == 0)
+ httpd_allow_origin = NULL;
+ // Test that the port is free. We do it here because we can make a nicer exit
+ // than we can in thread_init_cb(), where the actual binding takes place.
+ ret = bind_test(httpd_port);
+ if (ret < 0)
+ {
+ DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port);
return -1;
- }
+ }
- ret = rsp_init();
+ // Prepare modules, e.g. httpd_daap
+ ret = modules_init();
if (ret < 0)
{
- DPRINTF(E_FATAL, L_HTTPD, "RSP protocol init failed\n");
-
- goto rsp_fail;
- }
-
- ret = daap_init();
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "DAAP protocol init failed\n");
-
- goto daap_fail;
- }
-
- ret = dacp_init();
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "DACP protocol init failed\n");
-
- goto dacp_fail;
- }
-
- ret = jsonapi_init();
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "JSON api init failed\n");
-
- goto jsonapi_fail;
- }
-
- ret = artworkapi_init();
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Artwork init failed\n");
-
- goto artworkapi_fail;
- }
-
- ret = oauth_init();
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "OAuth init failed\n");
-
- goto oauth_fail;
+ DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n");
+ goto error;
}
#ifdef HAVE_LIBWEBSOCKETS
@@ -1738,119 +1439,28 @@ httpd_init(const char *webroot)
if (ret < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Websocket init failed\n");
-
- goto websocket_fail;
+ goto error;
}
#endif
- streaming_init();
-
-#ifdef HAVE_EVENTFD
- exit_efd = eventfd(0, EFD_CLOEXEC);
- if (exit_efd < 0)
+ httpd_threadpool = evthr_pool_wexit_new(THREADPOOL_NTHREADS, thread_init_cb, thread_exit_cb, NULL);
+ if (!httpd_threadpool)
{
- DPRINTF(E_FATAL, L_HTTPD, "Could not create eventfd: %s\n", strerror(errno));
-
- goto pipe_fail;
+ DPRINTF(E_LOG, L_HTTPD, "Could not create httpd thread pool\n");
+ goto error;
}
- exitev = event_new(evbase_httpd, exit_efd, EV_READ, exit_cb, NULL);
-#else
-# ifdef HAVE_PIPE2
- ret = pipe2(exit_pipe, O_CLOEXEC);
-# else
- ret = pipe(exit_pipe);
-# endif
+ ret = evthr_pool_start(httpd_threadpool);
if (ret < 0)
{
- DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno));
-
- goto pipe_fail;
+ DPRINTF(E_LOG, L_HTTPD, "Could not spawn worker threads\n");
+ goto error;
}
- exitev = event_new(evbase_httpd, exit_pipe[0], EV_READ, exit_cb, NULL);
-#endif /* HAVE_EVENTFD */
- if (!exitev)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not create exit event\n");
-
- goto exitev_fail;
- }
- event_add(exitev, NULL);
-
- evhttpd = evhttp_new(evbase_httpd);
- if (!evhttpd)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server\n");
-
- goto evhttpd_fail;
- }
-
- // For CORS headers
- allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
- if (allow_origin)
- {
- if (strlen(allow_origin) != 0)
- evhttp_set_allowed_methods(evhttpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
- else
- allow_origin = NULL;
- }
-
- httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
-
- ret = net_evhttp_bind(evhttpd, httpd_port, "httpd");
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (server already running?)\n", httpd_port);
- goto bind_fail;
- }
-
- evhttp_set_gencb(evhttpd, httpd_gen_cb, NULL);
-
- ret = pthread_create(&tid_httpd, NULL, httpd, NULL);
- if (ret != 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not spawn HTTPd thread: %s\n", strerror(errno));
-
- goto thread_fail;
- }
-
- thread_setname(tid_httpd, "httpd");
-
return 0;
- thread_fail:
- bind_fail:
- evhttp_free(evhttpd);
- evhttpd_fail:
- event_free(exitev);
- exitev_fail:
-#ifdef HAVE_EVENTFD
- close(exit_efd);
-#else
- close(exit_pipe[0]);
- close(exit_pipe[1]);
-#endif
- pipe_fail:
- streaming_deinit();
-#ifdef HAVE_LIBWEBSOCKETS
- websocket_deinit();
- websocket_fail:
-#endif
- oauth_deinit();
- oauth_fail:
- artworkapi_deinit();
- artworkapi_fail:
- jsonapi_deinit();
- jsonapi_fail:
- dacp_deinit();
- dacp_fail:
- daap_deinit();
- daap_fail:
- rsp_deinit();
- rsp_fail:
- event_base_free(evbase_httpd);
-
+ error:
+ httpd_deinit();
return -1;
}
@@ -1858,53 +1468,13 @@ httpd_init(const char *webroot)
void
httpd_deinit(void)
{
- int ret;
+ // Give modules a chance to hang up connections nicely
+ modules_deinit();
-#ifdef HAVE_EVENTFD
- ret = eventfd_write(exit_efd, 1);
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not send exit event: %s\n", strerror(errno));
-
- return;
- }
-#else
- int dummy = 42;
-
- ret = write(exit_pipe[1], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not write to exit fd: %s\n", strerror(errno));
-
- return;
- }
-#endif
-
- ret = pthread_join(tid_httpd, NULL);
- if (ret != 0)
- {
- DPRINTF(E_FATAL, L_HTTPD, "Could not join HTTPd thread: %s\n", strerror(errno));
-
- return;
- }
-
- streaming_deinit();
#ifdef HAVE_LIBWEBSOCKETS
websocket_deinit();
#endif
- oauth_deinit();
- jsonapi_deinit();
- rsp_deinit();
- dacp_deinit();
- daap_deinit();
-#ifdef HAVE_EVENTFD
- close(exit_efd);
-#else
- close(exit_pipe[0]);
- close(exit_pipe[1]);
-#endif
- event_free(exitev);
- evhttp_free(evhttpd);
- event_base_free(evbase_httpd);
+ evthr_pool_stop(httpd_threadpool);
+ evthr_pool_free(httpd_threadpool);
}
diff --git a/src/httpd.h b/src/httpd.h
index 1dd6e825..8926f707 100644
--- a/src/httpd.h
+++ b/src/httpd.h
@@ -2,112 +2,7 @@
#ifndef __HTTPD_H__
#define __HTTPD_H__
-#include
-#include
-#include
-#include
#include
-#include
-
-enum httpd_send_flags
-{
- HTTPD_SEND_NO_GZIP = (1 << 0),
-};
-
-/*
- * Contains a parsed version of the URI httpd got. The URI may have been
- * complete:
- * scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
- * or relative:
- * [/path][?query][#fragment]
- *
- * We are interested in the path and the query, so they are disassembled to
- * path_parts and ev_query. If the request is http://x:3689/foo/bar?key1=val1,
- * then part_parts[0] is "foo", [1] is "bar" and the rest is null.
- *
- * Each path_part is an allocated URI decoded string.
- */
-struct httpd_uri_parsed
-{
- const char *uri;
- struct evhttp_uri *ev_uri;
- struct evkeyvalq ev_query;
- char *uri_decoded;
- char *path;
- char *path_parts[31];
-};
-
-/*
- * A collection of pointers to request data that the reply handlers may need.
- * Also has the function pointer to the reply handler and a pointer to a reply
- * evbuffer.
- */
-struct httpd_request {
- // User-agent (if available)
- const char *user_agent;
- // The parsed request URI given to us by httpd_uri_parse
- struct httpd_uri_parsed *uri_parsed;
- // Shortcut to &uri_parsed->ev_query
- struct evkeyvalq *query;
- // http request struct (if available)
- struct evhttp_request *req;
- // Source IP address (ipv4 or ipv6) and port of the request (if available)
- const char *peer_address;
- unsigned short peer_port;
- // A pointer to extra data that the module handling the request might need
- void *extra_data;
-
- // Reply evbuffer
- struct evbuffer *reply;
-
- // A pointer to the handler that will process the request
- int (*handler)(struct httpd_request *hreq);
-};
-
-/*
- * Maps a regex of the request path to a handler of the request
- */
-struct httpd_uri_map
-{
- int method;
- char *regexp;
- int (*handler)(struct httpd_request *hreq);
- regex_t preg;
-};
-
-/*
- * Helper to free the parsed uri struct
- */
-void
-httpd_uri_free(struct httpd_uri_parsed *parsed);
-
-/*
- * Parse an URI into the struct
- */
-struct httpd_uri_parsed *
-httpd_uri_parse(const char *uri);
-
-/*
- * Parse a request into the httpd_request struct. It can later be freed with
- * free(), unless the module has allocated something to *extra_data. Note that
- * the pointers in the returned struct are only valid as long as the inputs are
- * still valid. If req is not null, then we will find the user-agent from the
- * request headers, except if provided as an argument to this function.
- */
-struct httpd_request *
-httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map);
-
-void
-httpd_stream_file(struct evhttp_request *req, int id);
-
-bool
-httpd_request_not_modified_since(struct evhttp_request *req, time_t mtime);
-
-bool
-httpd_request_etag_matches(struct evhttp_request *req, const char *etag);
-
-void
-httpd_response_not_cachable(struct evhttp_request *req);
/*
* Gzips an evbuffer
@@ -118,52 +13,6 @@ httpd_response_not_cachable(struct evhttp_request *req);
struct evbuffer *
httpd_gzip_deflate(struct evbuffer *in);
-/*
- * This wrapper around evhttp_send_reply should be used whenever a request may
- * come from a browser. It will automatically gzip if feasible, but the caller
- * may direct it not to. It will set CORS headers as appropriate. Should be
- * thread safe.
- *
- * @in req The evhttp request struct
- * @in code HTTP code, e.g. 200
- * @in reason A brief explanation of the error - if NULL the standard meaning
- of the error code will be used
- * @in evbuf Data for the response body
- * @in flags See flags above
- */
-void
-httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags);
-
-/*
- * This is a substitute for evhttp_send_error that should be used whenever an
- * error may be returned to a browser. It will set CORS headers as appropriate,
- * which is not possible with evhttp_send_error, because it clears the headers.
- * Should be thread safe.
- *
- * @in req The evhttp request struct
- * @in error HTTP code, e.g. 200
- * @in reason A brief explanation of the error - if NULL the standard meaning
- of the error code will be used
- */
-void
-httpd_send_error(struct evhttp_request *req, int error, const char *reason);
-
-/*
- * Redirects to the given path
- */
-void
-httpd_redirect_to(struct evhttp_request *req, const char *path);
-
-
-bool
-httpd_admin_check_auth(struct evhttp_request *req);
-
-int
-httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm);
-
-void
-httpd_peer_get(const char **address, ev_uint16_t *port, struct evhttp_connection *evcon);
-
int
httpd_init(const char *webroot);
diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c
index 4414d90b..48ed9161 100644
--- a/src/httpd_artworkapi.c
+++ b/src/httpd_artworkapi.c
@@ -20,12 +20,11 @@
# include
#endif
-#include
#include
#include
#include
-#include "httpd_artworkapi.h"
+#include "httpd_internal.h"
#include "logger.h"
#include "misc.h"
#include "player.h"
@@ -40,20 +39,20 @@ request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h)
*max_w = 0;
*max_h = 0;
- param = evhttp_find_header(hreq->query, "maxwidth");
+ param = httpd_query_value_find(hreq->query, "maxwidth");
if (param)
{
ret = safe_atou32(param, max_w);
if (ret < 0)
- DPRINTF(E_LOG, L_WEB, "Invalid width in request: '%s'\n", hreq->uri_parsed->uri);
+ DPRINTF(E_LOG, L_WEB, "Invalid width in request: '%s'\n", hreq->uri);
}
- param = evhttp_find_header(hreq->query, "maxheight");
+ param = httpd_query_value_find(hreq->query, "maxheight");
if (param)
{
ret = safe_atou32(param, max_h);
if (ret < 0)
- DPRINTF(E_LOG, L_WEB, "Invalid height in request: '%s'\n", hreq->uri_parsed->uri);
+ DPRINTF(E_LOG, L_WEB, "Invalid height in request: '%s'\n", hreq->uri);
}
return 0;
@@ -62,14 +61,10 @@ request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h)
static int
response_process(struct httpd_request *hreq, int format)
{
- struct evkeyvalq *headers;
-
- headers = evhttp_request_get_output_headers(hreq->req);
-
if (format == ART_FMT_PNG)
- evhttp_add_header(headers, "Content-Type", "image/png");
+ httpd_header_add(hreq->out_headers, "Content-Type", "image/png");
else if (format == ART_FMT_JPEG)
- evhttp_add_header(headers, "Content-Type", "image/jpeg");
+ httpd_header_add(hreq->out_headers, "Content-Type", "image/jpeg");
else
return HTTP_NOCONTENT;
@@ -92,7 +87,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq)
if (ret != 0)
return HTTP_NOTFOUND;
- ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
+ ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@@ -109,11 +104,11 @@ artworkapi_reply_item(struct httpd_request *hreq)
if (ret != 0)
return ret;
- ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
+ ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0)
return HTTP_BADREQUEST;
- ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
+ ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@@ -130,111 +125,73 @@ artworkapi_reply_group(struct httpd_request *hreq)
if (ret != 0)
return ret;
- ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
+ ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0)
return HTTP_BADREQUEST;
- ret = artwork_get_group(hreq->reply, id, max_w, max_h, 0);
+ ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
static struct httpd_uri_map artworkapi_handlers[] =
{
- { EVHTTP_REQ_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying },
- { EVHTTP_REQ_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item },
- { EVHTTP_REQ_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group },
+ { HTTPD_METHOD_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying },
+ { HTTPD_METHOD_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item },
+ { HTTPD_METHOD_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group },
{ 0, NULL, NULL }
};
/* ------------------------------- API --------------------------------- */
-void
-artworkapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+
+static void
+artworkapi_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
int status_code;
- DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", uri_parsed->uri);
-
- if (!httpd_admin_check_auth(req))
+ if (!httpd_admin_check_auth(hreq))
return;
- hreq = httpd_request_parse(req, uri_parsed, NULL, artworkapi_handlers);
- if (!hreq)
+ if (!hreq->handler)
{
- DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in artwork api request: '%s'\n", uri_parsed->path, uri_parsed->uri);
+ DPRINTF(E_LOG, L_WEB, "Unrecognized path in artwork api request: '%s'\n", hreq->uri);
- httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return;
}
- CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
-
status_code = hreq->handler(hreq);
switch (status_code)
{
case HTTP_OK: /* 200 OK */
- httpd_send_reply(req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, status_code, "OK", HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOCONTENT: /* 204 No Content */
- httpd_send_reply(req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, status_code, "No Content", HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOTMODIFIED: /* 304 Not Modified */
- httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, HTTPD_SEND_NO_GZIP);
break;
case HTTP_BADREQUEST: /* 400 Bad Request */
- httpd_send_error(req, status_code, "Bad Request");
+ httpd_send_error(hreq, status_code, "Bad Request");
break;
case HTTP_NOTFOUND: /* 404 Not Found */
- httpd_send_error(req, status_code, "Not Found");
+ httpd_send_error(hreq, status_code, "Not Found");
break;
case HTTP_INTERNAL: /* 500 Internal Server Error */
default:
- httpd_send_error(req, HTTP_INTERNAL, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
}
-
- evbuffer_free(hreq->reply);
- free(hreq);
}
-int
-artworkapi_is_request(const char *path)
+struct httpd_module httpd_artworkapi =
{
- if (strncmp(path, "/artwork/", strlen("/artwork/")) == 0)
- return 1;
-
- return 0;
-}
-
-int
-artworkapi_init(void)
-{
- char buf[64];
- int i;
- int ret;
-
- for (i = 0; artworkapi_handlers[i].handler; i++)
- {
- ret = regcomp(&artworkapi_handlers[i].preg, artworkapi_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &artworkapi_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_WEB, "artwork api init failed; regexp error: %s\n", buf);
- return -1;
- }
- }
-
- return 0;
-}
-
-void
-artworkapi_deinit(void)
-{
- int i;
-
- for (i = 0; artworkapi_handlers[i].handler; i++)
- regfree(&artworkapi_handlers[i].preg);
-}
+ .name = "Artwork API",
+ .type = MODULE_ARTWORKAPI,
+ .logdomain = L_WEB,
+ .subpaths = { "/artwork/", NULL },
+ .handlers = artworkapi_handlers,
+ .request = artworkapi_request,
+};
diff --git a/src/httpd_artworkapi.h b/src/httpd_artworkapi.h
deleted file mode 100644
index 7c7163c5..00000000
--- a/src/httpd_artworkapi.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __HTTPD_ARTWORK_H__
-#define __HTTPD_ARTWORK_H__
-
-#include "httpd.h"
-
-int
-artworkapi_init(void);
-
-void
-artworkapi_deinit(void);
-
-void
-artworkapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-artworkapi_is_request(const char *path);
-
-#endif
diff --git a/src/httpd_daap.c b/src/httpd_daap.c
index e87d1f45..5fed4db9 100644
--- a/src/httpd_daap.c
+++ b/src/httpd_daap.c
@@ -42,8 +42,8 @@
#include
#include
-#include
+#include "httpd_internal.h"
#include "httpd_daap.h"
#include "logger.h"
#include "db.h"
@@ -55,9 +55,6 @@
#include "cache.h"
-/* httpd event base, from httpd.c */
-extern struct event_base *evbase_httpd;
-
/* Max number of sessions and session timeout
* Many clients (including iTunes) don't seem to respect the timeout capability
* that we announce, and just keep using the same session. Therefore we take a
@@ -99,7 +96,7 @@ struct daap_session {
};
struct daap_update_request {
- struct evhttp_request *req;
+ struct httpd_request *hreq;
/* Refresh tiemout */
struct event *timeout;
@@ -246,12 +243,14 @@ daap_session_add(bool is_remote, int request_session_id)
return s;
}
-
/* ---------------------- UPDATE REQUESTS HANDLERS -------------------------- */
static void
update_free(struct daap_update_request *ur)
{
+ if (!ur)
+ return;
+
if (ur->timeout)
event_free(ur->timeout);
@@ -285,45 +284,28 @@ update_remove(struct daap_update_request *ur)
static void
update_refresh_cb(int fd, short event, void *arg)
{
- struct daap_update_request *ur;
- struct evhttp_connection *evcon;
- struct evbuffer *reply;
-
- ur = (struct daap_update_request *)arg;
-
- CHECK_NULL(L_DAAP, reply = evbuffer_new());
- CHECK_ERR(L_DAAP, evbuffer_expand(reply, 32));
+ struct daap_update_request *ur = arg;
+ struct httpd_request *hreq = ur->hreq;
current_rev++;
/* Send back current revision */
- dmap_add_container(reply, "mupd", 24);
- dmap_add_int(reply, "mstt", 200); /* 12 */
- dmap_add_int(reply, "musr", current_rev); /* 12 */
+ dmap_add_container(hreq->out_body, "mupd", 24);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "musr", current_rev); /* 12 */
- evcon = evhttp_request_get_connection(ur->req);
- evhttp_connection_set_closecb(evcon, NULL, NULL);
-
- httpd_send_reply(ur->req, HTTP_OK, "OK", reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
update_remove(ur);
}
static void
-update_fail_cb(struct evhttp_connection *evcon, void *arg)
+update_fail_cb(void *arg)
{
- struct evhttp_connection *evc;
- struct daap_update_request *ur;
-
- ur = (struct daap_update_request *)arg;
+ struct daap_update_request *ur = arg;
DPRINTF(E_DBG, L_DAAP, "Update request: client closed connection\n");
- evc = evhttp_request_get_connection(ur->req);
- if (evc)
- evhttp_connection_set_closecb(evc, NULL, NULL);
-
- evhttp_request_free(ur->req);
update_remove(ur);
}
@@ -513,7 +495,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct httpd_reques
memset(qp, 0, sizeof(struct query_params));
- param = evhttp_find_header(hreq->query, "index");
+ param = httpd_query_value_find(hreq->query, "index");
if (param)
{
if (param[0] == '-') /* -n, last n entries */
@@ -559,7 +541,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct httpd_reques
qp->idx_type = I_SUB;
qp->sort = S_NONE;
- param = evhttp_find_header(hreq->query, "sort");
+ param = httpd_query_value_find(hreq->query, "sort");
if (param)
{
if (strcmp(param, "name") == 0)
@@ -580,7 +562,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct httpd_reques
if (sort_headers)
{
*sort_headers = 0;
- param = evhttp_find_header(hreq->query, "include-sort-headers");
+ param = httpd_query_value_find(hreq->query, "include-sort-headers");
if (param && (strcmp(param, "1") == 0))
{
*sort_headers = 1;
@@ -588,9 +570,9 @@ query_params_set(struct query_params *qp, int *sort_headers, struct httpd_reques
}
}
- param = evhttp_find_header(hreq->query, "query");
+ param = httpd_query_value_find(hreq->query, "query");
if (!param)
- param = evhttp_find_header(hreq->query, "filter");
+ param = httpd_query_value_find(hreq->query, "filter");
if (param)
{
@@ -675,26 +657,26 @@ daap_reply_send(struct httpd_request *hreq, enum daap_reply_result result)
switch (result)
{
case DAAP_REPLY_LOGOUT:
- httpd_send_reply(hreq->req, 204, "Logout Successful", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "Logout Successful", 0);
break;
case DAAP_REPLY_NO_CONTENT:
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
break;
case DAAP_REPLY_OK:
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
break;
case DAAP_REPLY_OK_NO_GZIP:
case DAAP_REPLY_ERROR:
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
break;
case DAAP_REPLY_FORBIDDEN:
- httpd_send_error(hreq->req, 403, "Forbidden");
+ httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden");
break;
case DAAP_REPLY_BAD_REQUEST:
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
break;
case DAAP_REPLY_SERVUNAVAIL:
- httpd_send_error(hreq->req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
break;
case DAAP_REPLY_NO_CONNECTION:
case DAAP_REPLY_NONE:
@@ -718,17 +700,17 @@ daap_request_authorize(struct httpd_request *hreq)
// with httpd_basic_auth() if a library password is set. Remote clients will
// also call /login, but they should not get a httpd_basic_auth(), instead
// daap_reply_login() will take care of auth.
- if (session->is_remote && (strcmp(hreq->uri_parsed->path, "/login") == 0))
+ if (session->is_remote && (strcmp(hreq->path, "/login") == 0))
return 0;
- param = evhttp_find_header(hreq->query, "session-id");
+ param = httpd_query_value_find(hreq->query, "session-id");
if (param)
{
if (session->id == 0)
{
- DPRINTF(E_LOG, L_DAAP, "Unauthorized request from '%s', DAAP session not found: '%s'\n", hreq->peer_address, hreq->uri_parsed->uri);
+ DPRINTF(E_LOG, L_DAAP, "Unauthorized request from '%s', DAAP session not found: '%s'\n", hreq->peer_address, hreq->uri);
- httpd_send_error(hreq->req, 401, "Unauthorized");
+ httpd_send_error(hreq, HTTP_UNAUTHORIZED, "Unauthorized");;
return -1;
}
@@ -741,16 +723,16 @@ daap_request_authorize(struct httpd_request *hreq)
return 0;
// If no valid session then we may need to authenticate
- if ((strcmp(hreq->uri_parsed->path, "/server-info") == 0)
- || (strcmp(hreq->uri_parsed->path, "/logout") == 0)
- || (strcmp(hreq->uri_parsed->path, "/content-codes") == 0)
- || (strncmp(hreq->uri_parsed->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
+ if ((strcmp(hreq->path, "/server-info") == 0)
+ || (strcmp(hreq->path, "/logout") == 0)
+ || (strcmp(hreq->path, "/content-codes") == 0)
+ || (strncmp(hreq->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
return 0; // No authentication
DPRINTF(E_DBG, L_DAAP, "Checking authentication for library\n");
// We don't care about the username
- ret = httpd_basic_auth(hreq->req, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
+ ret = httpd_basic_auth(hreq, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
if (ret != 0)
{
DPRINTF(E_LOG, L_DAAP, "Unsuccessful library authorization attempt from '%s'\n", hreq->peer_address);
@@ -764,13 +746,12 @@ daap_request_authorize(struct httpd_request *hreq)
/* --------------------------- REPLY HANDLERS ------------------------------- */
/* Note that some handlers can be called without a connection (needed for */
/* cache regeneration), while others cannot. Those that cannot should check */
-/* that hreq->req is not a null pointer. */
+/* that hreq->backend is not null. */
static enum daap_reply_result
daap_reply_server_info(struct httpd_request *hreq)
{
struct evbuffer *content;
- struct evkeyvalq *headers;
char *name;
char *passwd;
const char *clientver;
@@ -778,7 +759,7 @@ daap_reply_server_info(struct httpd_request *hreq)
int mpro;
int apro;
- if (!hreq->req)
+ if (!hreq->backend)
{
DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_server_info() cannot be called without an actual connection\n");
return DAAP_REPLY_NO_CONNECTION;
@@ -793,8 +774,7 @@ daap_reply_server_info(struct httpd_request *hreq)
mpro = 2 << 16 | 10;
apro = 3 << 16 | 12;
- headers = evhttp_request_get_input_headers(hreq->req);
- if (headers && (clientver = evhttp_find_header(headers, "Client-DAAP-Version")))
+ if (hreq->in_headers && (clientver = httpd_header_find(hreq->in_headers, "Client-DAAP-Version")))
{
if (strcmp(clientver, "1.0") == 0)
{
@@ -864,9 +844,9 @@ daap_reply_server_info(struct httpd_request *hreq)
// Create container
len = evbuffer_get_length(content);
- dmap_add_container(hreq->reply, "msrv", len);
+ dmap_add_container(hreq->out_body, "msrv", len);
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, content));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, content));
evbuffer_free(content);
@@ -887,19 +867,19 @@ daap_reply_content_codes(struct httpd_request *hreq)
for (i = 0; i < nfields; i++)
len += 8 + 12 + 10 + 8 + strlen(dmap_fields[i].desc);
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, len + 8));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, len + 8));
- dmap_add_container(hreq->reply, "mccr", len);
- dmap_add_int(hreq->reply, "mstt", 200);
+ dmap_add_container(hreq->out_body, "mccr", len);
+ dmap_add_int(hreq->out_body, "mstt", 200);
for (i = 0; i < nfields; i++)
{
len = 12 + 10 + 8 + strlen(dmap_fields[i].desc);
- dmap_add_container(hreq->reply, "mdcl", len);
- dmap_add_string(hreq->reply, "mcnm", dmap_fields[i].tag); /* 12 */
- dmap_add_string(hreq->reply, "mcna", dmap_fields[i].desc); /* 8 + strlen(desc) */
- dmap_add_short(hreq->reply, "mcty", dmap_fields[i].type); /* 10 */
+ dmap_add_container(hreq->out_body, "mdcl", len);
+ dmap_add_string(hreq->out_body, "mcnm", dmap_fields[i].tag); /* 12 */
+ dmap_add_string(hreq->out_body, "mcna", dmap_fields[i].desc); /* 8 + strlen(desc) */
+ dmap_add_short(hreq->out_body, "mcty", dmap_fields[i].type); /* 10 */
}
return DAAP_REPLY_OK;
@@ -915,9 +895,9 @@ daap_reply_login(struct httpd_request *hreq)
int request_session_id;
int ret;
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 32));
- param = evhttp_find_header(hreq->query, "pairing-guid");
+ param = httpd_query_value_find(hreq->query, "pairing-guid");
if (param && !net_peer_address_is_trusted(hreq->peer_address))
{
if (strlen(param) < 3)
@@ -948,7 +928,7 @@ daap_reply_login(struct httpd_request *hreq)
DPRINTF(E_INFO, L_DAAP, "Client (unknown user-agent) logging in from %s\n", hreq->peer_address);
}
- param = evhttp_find_header(hreq->query, "request-session-id");
+ param = httpd_query_value_find(hreq->query, "request-session-id");
if (param)
{
ret = safe_atoi32(param, &request_session_id);
@@ -964,13 +944,13 @@ daap_reply_login(struct httpd_request *hreq)
session = daap_session_add(adhoc->is_remote, request_session_id);
if (!session)
{
- dmap_error_make(hreq->reply, "mlog", "Could not start session");
+ dmap_error_make(hreq->out_body, "mlog", "Could not start session");
return DAAP_REPLY_ERROR;
}
- dmap_add_container(hreq->reply, "mlog", 24);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "mlid", session->id); /* 12 */
+ dmap_add_container(hreq->out_body, "mlog", 24);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "mlid", session->id); /* 12 */
return DAAP_REPLY_OK;
}
@@ -992,19 +972,17 @@ static enum daap_reply_result
daap_reply_update(struct httpd_request *hreq)
{
struct daap_update_request *ur;
- struct evhttp_connection *evcon;
- struct bufferevent *bufev;
const char *param;
int reqd_rev;
int ret;
- if (!hreq->req)
+ if (!hreq->backend)
{
DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_update() cannot be called without an actual connection\n");
return DAAP_REPLY_NO_CONNECTION;
}
- param = evhttp_find_header(hreq->query, "revision-number");
+ param = httpd_query_value_find(hreq->query, "revision-number");
if (!param)
{
DPRINTF(E_DBG, L_DAAP, "Missing revision-number in client update request\n");
@@ -1018,18 +996,18 @@ daap_reply_update(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Parameter revision-number not an integer\n");
- dmap_error_make(hreq->reply, "mupd", "Invalid request");
+ dmap_error_make(hreq->out_body, "mupd", "Invalid request");
return DAAP_REPLY_ERROR;
}
if (reqd_rev == 1) /* Or revision is not valid */
{
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 32));
/* Send back current revision */
- dmap_add_container(hreq->reply, "mupd", 24);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "musr", current_rev); /* 12 */
+ dmap_add_container(hreq->out_body, "mupd", 24);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "musr", current_rev); /* 12 */
return DAAP_REPLY_OK;
}
@@ -1040,13 +1018,13 @@ daap_reply_update(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Out of memory for update request\n");
- dmap_error_make(hreq->reply, "mupd", "Out of memory");
+ dmap_error_make(hreq->out_body, "mupd", "Out of memory");
return DAAP_REPLY_ERROR;
}
if (DAAP_UPDATE_REFRESH > 0)
{
- ur->timeout = evtimer_new(evbase_httpd, update_refresh_cb, ur);
+ ur->timeout = evtimer_new(hreq->evbase, update_refresh_cb, ur);
if (ur->timeout)
ret = evtimer_add(ur->timeout, &daap_update_refresh_tv);
else
@@ -1056,14 +1034,14 @@ daap_reply_update(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Out of memory for update request event\n");
- dmap_error_make(hreq->reply, "mupd", "Could not register timer");
+ dmap_error_make(hreq->out_body, "mupd", "Could not register timer");
update_free(ur);
return DAAP_REPLY_ERROR;
}
}
/* NOTE: we may need to keep reqd_rev in there too */
- ur->req = hreq->req;
+ ur->hreq = hreq;
ur->next = update_requests;
update_requests = ur;
@@ -1071,20 +1049,7 @@ daap_reply_update(struct httpd_request *hreq)
/* If the connection fails before we have an update to push out
* to the client, we need to know.
*/
- evcon = evhttp_request_get_connection(hreq->req);
- if (evcon)
- {
- evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
-
- // This is a workaround for some versions of libevent (2.0, but possibly
- // also 2.1) that don't detect if the client hangs up, and thus don't
- // clean up and never call update_fail_cb(). See github issue #870 and
- // https://github.com/libevent/libevent/issues/666. It should probably be
- // removed again in the future. The workaround is also present in dacp.c
- bufev = evhttp_connection_get_bufferevent(evcon);
- if (bufev)
- bufferevent_enable(bufev, EV_READ);
- }
+ httpd_request_close_cb_set(hreq, update_fail_cb, ur);
return DAAP_REPLY_NONE;
}
@@ -1112,7 +1077,7 @@ daap_reply_dblist(struct httpd_request *hreq)
CHECK_NULL(L_DAAP, content = evbuffer_new());
CHECK_NULL(L_DAAP, item = evbuffer_new());
CHECK_ERR(L_DAAP, evbuffer_expand(item, 512));
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 1024));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 1024));
// Add db entry for library with dbid = 1
dmap_add_int(item, "miid", 1);
@@ -1155,14 +1120,14 @@ daap_reply_dblist(struct httpd_request *hreq)
// Create container
len = evbuffer_get_length(content);
- dmap_add_container(hreq->reply, "avdb", len + 53);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_char(hreq->reply, "muty", 0); /* 9 */
- dmap_add_int(hreq->reply, "mtco", 2); /* 12 */
- dmap_add_int(hreq->reply, "mrco", 2); /* 12 */
- dmap_add_container(hreq->reply, "mlcl", len); /* 8 */
+ dmap_add_container(hreq->out_body, "avdb", len + 53);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_char(hreq->out_body, "muty", 0); /* 9 */
+ dmap_add_int(hreq->out_body, "mtco", 2); /* 12 */
+ dmap_add_int(hreq->out_body, "mrco", 2); /* 12 */
+ dmap_add_container(hreq->out_body, "mlcl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, content));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, content));
evbuffer_free(item);
evbuffer_free(content);
@@ -1177,7 +1142,6 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
struct db_media_file_info dbmfi;
struct evbuffer *song;
struct evbuffer *songlist;
- struct evkeyvalq *headers;
struct daap_session *s;
const struct dmap_field **meta = NULL;
struct sort_ctx *sctx;
@@ -1218,11 +1182,11 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
CHECK_NULL(L_DAAP, songlist = evbuffer_new());
CHECK_NULL(L_DAAP, song = evbuffer_new());
CHECK_NULL(L_DAAP, sctx = daap_sort_context_new());
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 61));
CHECK_ERR(L_DAAP, evbuffer_expand(songlist, 4096));
CHECK_ERR(L_DAAP, evbuffer_expand(song, 512));
- param = evhttp_find_header(hreq->query, "meta");
+ param = httpd_query_value_find(hreq->query, "meta");
if (!param)
{
DPRINTF(E_DBG, L_DAAP, "No meta parameter in query, using default\n");
@@ -1246,15 +1210,14 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
{
DPRINTF(E_LOG, L_DAAP, "Could not start query\n");
- dmap_error_make(hreq->reply, tag, "Could not start query");
+ dmap_error_make(hreq->out_body, tag, "Could not start query");
goto error;
}
client_codecs = NULL;
- if (!s->is_remote && hreq->req)
+ if (!s->is_remote && hreq->in_headers)
{
- headers = evhttp_request_get_input_headers(hreq->req);
- client_codecs = evhttp_find_header(headers, "Accept-Codecs");
+ client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
}
nsongs = 0;
@@ -1312,13 +1275,13 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
if (ret == -100)
{
- dmap_error_make(hreq->reply, tag, "Out of memory");
+ dmap_error_make(hreq->out_body, tag, "Out of memory");
goto error;
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Error fetching results\n");
- dmap_error_make(hreq->reply, tag, "Error fetching query results");
+ dmap_error_make(hreq->out_body, tag, "Error fetching query results");
goto error;
}
@@ -1327,25 +1290,25 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
if (sort_headers)
{
daap_sort_finalize(sctx);
- dmap_add_container(hreq->reply, tag, len + evbuffer_get_length(sctx->headerlist) + 61);
+ dmap_add_container(hreq->out_body, tag, len + evbuffer_get_length(sctx->headerlist) + 61);
}
else
- dmap_add_container(hreq->reply, tag, len + 53);
+ dmap_add_container(hreq->out_body, tag, len + 53);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_char(hreq->reply, "muty", 0); /* 9 */
- dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */
- dmap_add_int(hreq->reply, "mrco", nsongs); /* 12 */
- dmap_add_container(hreq->reply, "mlcl", len); /* 8 */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_char(hreq->out_body, "muty", 0); /* 9 */
+ dmap_add_int(hreq->out_body, "mtco", qp.results); /* 12 */
+ dmap_add_int(hreq->out_body, "mrco", nsongs); /* 12 */
+ dmap_add_container(hreq->out_body, "mlcl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, songlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, songlist));
if (sort_headers)
{
len = evbuffer_get_length(sctx->headerlist);
- dmap_add_container(hreq->reply, "mshl", len); /* 8 */
+ dmap_add_container(hreq->out_body, "mshl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, sctx->headerlist));
}
free(meta);
@@ -1378,10 +1341,10 @@ daap_reply_plsonglist(struct httpd_request *hreq)
int playlist;
int ret;
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist);
+ ret = safe_atoi32(hreq->path_parts[3], &playlist);
if (ret < 0)
{
- dmap_error_make(hreq->reply, "apso", "Invalid playlist ID");
+ dmap_error_make(hreq->out_body, "apso", "Invalid playlist ID");
return DAAP_REPLY_ERROR;
}
@@ -1424,10 +1387,10 @@ daap_reply_playlists(struct httpd_request *hreq)
cfg_radiopl = cfg_getbool(cfg_getsec(cfg, "library"), "radio_playlists");
- ret = safe_atoi32(hreq->uri_parsed->path_parts[1], &database);
+ ret = safe_atoi32(hreq->path_parts[1], &database);
if (ret < 0)
{
- dmap_error_make(hreq->reply, "aply", "Invalid database ID");
+ dmap_error_make(hreq->out_body, "aply", "Invalid database ID");
return DAAP_REPLY_ERROR;
}
@@ -1436,11 +1399,11 @@ daap_reply_playlists(struct httpd_request *hreq)
CHECK_NULL(L_DAAP, playlistlist = evbuffer_new());
CHECK_NULL(L_DAAP, playlist = evbuffer_new());
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 61));
CHECK_ERR(L_DAAP, evbuffer_expand(playlistlist, 1024));
CHECK_ERR(L_DAAP, evbuffer_expand(playlist, 128));
- param = evhttp_find_header(hreq->query, "meta");
+ param = httpd_query_value_find(hreq->query, "meta");
if (!param)
{
DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n");
@@ -1453,7 +1416,7 @@ daap_reply_playlists(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Failed to parse meta parameter in DAAP query\n");
- dmap_error_make(hreq->reply, "aply", "Failed to parse query");
+ dmap_error_make(hreq->out_body, "aply", "Failed to parse query");
goto error;
}
@@ -1462,7 +1425,7 @@ daap_reply_playlists(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Could not start query\n");
- dmap_error_make(hreq->reply, "aply", "Could not start query");
+ dmap_error_make(hreq->out_body, "aply", "Could not start query");
goto error;
}
@@ -1578,26 +1541,26 @@ daap_reply_playlists(struct httpd_request *hreq)
if (ret == -100)
{
- dmap_error_make(hreq->reply, "aply", "Out of memory");
+ dmap_error_make(hreq->out_body, "aply", "Out of memory");
goto error;
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Error fetching results\n");
- dmap_error_make(hreq->reply, "aply", "Error fetching query results");
+ dmap_error_make(hreq->out_body, "aply", "Error fetching query results");
goto error;
}
/* Add header to evbuf, add playlistlist to evbuf */
len = evbuffer_get_length(playlistlist);
- dmap_add_container(hreq->reply, "aply", len + 53);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_char(hreq->reply, "muty", 0); /* 9 */
- dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */
- dmap_add_int(hreq->reply,"mrco", npls); /* 12 */
- dmap_add_container(hreq->reply, "mlcl", len);
+ dmap_add_container(hreq->out_body, "aply", len + 53);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_char(hreq->out_body, "muty", 0); /* 9 */
+ dmap_add_int(hreq->out_body, "mtco", qp.results); /* 12 */
+ dmap_add_int(hreq->out_body,"mrco", npls); /* 12 */
+ dmap_add_container(hreq->out_body, "mlcl", len);
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, playlistlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, playlistlist));
free(meta);
evbuffer_free(playlist);
@@ -1638,7 +1601,7 @@ daap_reply_groups(struct httpd_request *hreq)
int i;
int ret;
- param = evhttp_find_header(hreq->query, "group-type");
+ param = httpd_query_value_find(hreq->query, "group-type");
if (param && strcmp(param, "artists") == 0)
{
// Request from Remote may have the form:
@@ -1662,11 +1625,11 @@ daap_reply_groups(struct httpd_request *hreq)
CHECK_NULL(L_DAAP, grouplist = evbuffer_new());
CHECK_NULL(L_DAAP, group = evbuffer_new());
CHECK_NULL(L_DAAP, sctx = daap_sort_context_new());
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 61));
CHECK_ERR(L_DAAP, evbuffer_expand(grouplist, 1024));
CHECK_ERR(L_DAAP, evbuffer_expand(group, 128));
- param = evhttp_find_header(hreq->query, "meta");
+ param = httpd_query_value_find(hreq->query, "meta");
if (!param)
{
DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n");
@@ -1679,7 +1642,7 @@ daap_reply_groups(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Failed to parse meta parameter in DAAP query\n");
- dmap_error_make(hreq->reply, tag, "Failed to parse query");
+ dmap_error_make(hreq->out_body, tag, "Failed to parse query");
goto error;
}
@@ -1688,7 +1651,7 @@ daap_reply_groups(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Could not start query\n");
- dmap_error_make(hreq->reply, tag, "Could not start query");
+ dmap_error_make(hreq->out_body, tag, "Could not start query");
goto error;
}
@@ -1780,13 +1743,13 @@ daap_reply_groups(struct httpd_request *hreq)
if (ret == -100)
{
- dmap_error_make(hreq->reply, tag, "Out of memory");
+ dmap_error_make(hreq->out_body, tag, "Out of memory");
goto error;
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Error fetching results\n");
- dmap_error_make(hreq->reply, tag, "Error fetching query results");
+ dmap_error_make(hreq->out_body, tag, "Error fetching query results");
goto error;
}
@@ -1795,25 +1758,25 @@ daap_reply_groups(struct httpd_request *hreq)
if (sort_headers)
{
daap_sort_finalize(sctx);
- dmap_add_container(hreq->reply, tag, len + evbuffer_get_length(sctx->headerlist) + 61);
+ dmap_add_container(hreq->out_body, tag, len + evbuffer_get_length(sctx->headerlist) + 61);
}
else
- dmap_add_container(hreq->reply, tag, len + 53);
+ dmap_add_container(hreq->out_body, tag, len + 53);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_char(hreq->reply, "muty", 0); /* 9 */
- dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */
- dmap_add_int(hreq->reply,"mrco", ngrp); /* 12 */
- dmap_add_container(hreq->reply, "mlcl", len); /* 8 */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_char(hreq->out_body, "muty", 0); /* 9 */
+ dmap_add_int(hreq->out_body, "mtco", qp.results); /* 12 */
+ dmap_add_int(hreq->out_body,"mrco", ngrp); /* 12 */
+ dmap_add_container(hreq->out_body, "mlcl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, grouplist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, grouplist));
if (sort_headers)
{
len = evbuffer_get_length(sctx->headerlist);
- dmap_add_container(hreq->reply, "mshl", len); /* 8 */
+ dmap_add_container(hreq->out_body, "mshl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, sctx->headerlist));
}
free(meta);
@@ -1848,36 +1811,36 @@ daap_reply_browse(struct httpd_request *hreq)
int nitems;
int ret;
- if (strcmp(hreq->uri_parsed->path_parts[3], "artists") == 0)
+ if (strcmp(hreq->path_parts[3], "artists") == 0)
{
tag = "abar";
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ARTISTS);
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "albums") == 0)
+ else if (strcmp(hreq->path_parts[3], "albums") == 0)
{
tag = "abal";
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ALBUMS);
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "genres") == 0)
+ else if (strcmp(hreq->path_parts[3], "genres") == 0)
{
tag = "abgn";
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_GENRES);
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "composers") == 0)
+ else if (strcmp(hreq->path_parts[3], "composers") == 0)
{
tag = "abcp";
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_COMPOSERS);
}
else
{
- DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", hreq->uri_parsed->path_parts[3]);
- dmap_error_make(hreq->reply, "abro", "Invalid browse type");
+ DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", hreq->path_parts[3]);
+ dmap_error_make(hreq->out_body, "abro", "Invalid browse type");
return DAAP_REPLY_ERROR;
}
CHECK_NULL(L_DAAP, itemlist = evbuffer_new());
CHECK_NULL(L_DAAP, sctx = daap_sort_context_new());
- CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 52));
+ CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 52));
CHECK_ERR(L_DAAP, evbuffer_expand(itemlist, 1024)); // Just a starting alloc, it'll expand as needed
ret = db_query_start(&qp);
@@ -1885,7 +1848,7 @@ daap_reply_browse(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Could not start query\n");
- dmap_error_make(hreq->reply, "abro", "Could not start query");
+ dmap_error_make(hreq->out_body, "abro", "Could not start query");
goto error;
}
@@ -1913,7 +1876,7 @@ daap_reply_browse(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DAAP, "Error fetching/building results\n");
- dmap_error_make(hreq->reply, "abro", "Error fetching/building query results");
+ dmap_error_make(hreq->out_body, "abro", "Error fetching/building query results");
goto error;
}
@@ -1921,24 +1884,24 @@ daap_reply_browse(struct httpd_request *hreq)
if (sort_headers)
{
daap_sort_finalize(sctx);
- dmap_add_container(hreq->reply, "abro", len + evbuffer_get_length(sctx->headerlist) + 52);
+ dmap_add_container(hreq->out_body, "abro", len + evbuffer_get_length(sctx->headerlist) + 52);
}
else
- dmap_add_container(hreq->reply, "abro", len + 44);
+ dmap_add_container(hreq->out_body, "abro", len + 44);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */
- dmap_add_int(hreq->reply, "mrco", nitems); /* 12 */
- dmap_add_container(hreq->reply, tag, len); /* 8 */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "mtco", qp.results); /* 12 */
+ dmap_add_int(hreq->out_body, "mrco", nitems); /* 12 */
+ dmap_add_container(hreq->out_body, tag, len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, itemlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, itemlist));
if (sort_headers)
{
len = evbuffer_get_length(sctx->headerlist);
- dmap_add_container(hreq->reply, "mshl", len); /* 8 */
+ dmap_add_container(hreq->out_body, "mshl", len); /* 8 */
- CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist));
+ CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->out_body, sctx->headerlist));
}
daap_sort_context_free(sctx);
@@ -1959,7 +1922,6 @@ daap_reply_browse(struct httpd_request *hreq)
static enum daap_reply_result
daap_reply_extra_data(struct httpd_request *hreq)
{
- struct evkeyvalq *headers;
char clen[32];
const char *param;
char *ctype;
@@ -1969,22 +1931,22 @@ daap_reply_extra_data(struct httpd_request *hreq)
int max_h;
int ret;
- if (!hreq->req)
+ if (!hreq->backend)
{
DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_extra_data() cannot be called without an actual connection\n");
return DAAP_REPLY_NO_CONNECTION;
}
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id);
+ ret = safe_atoi32(hreq->path_parts[3], &id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", hreq->uri_parsed->path_parts[3]);
+ DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", hreq->path_parts[3]);
return DAAP_REPLY_BAD_REQUEST;
}
- if (evhttp_find_header(hreq->query, "mw") && evhttp_find_header(hreq->query, "mh"))
+ if (httpd_query_value_find(hreq->query, "mw") && httpd_query_value_find(hreq->query, "mh"))
{
- param = evhttp_find_header(hreq->query, "mw");
+ param = httpd_query_value_find(hreq->query, "mw");
ret = safe_atoi32(param, &max_w);
if (ret < 0)
{
@@ -1992,7 +1954,7 @@ daap_reply_extra_data(struct httpd_request *hreq)
return DAAP_REPLY_BAD_REQUEST;
}
- param = evhttp_find_header(hreq->query, "mh");
+ param = httpd_query_value_find(hreq->query, "mh");
ret = safe_atoi32(param, &max_h);
if (ret < 0)
{
@@ -2008,12 +1970,12 @@ daap_reply_extra_data(struct httpd_request *hreq)
max_h = 0;
}
- if (strcmp(hreq->uri_parsed->path_parts[2], "groups") == 0)
- ret = artwork_get_group(hreq->reply, id, max_w, max_h, 0);
- else if (strcmp(hreq->uri_parsed->path_parts[2], "items") == 0)
- ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
+ if (strcmp(hreq->path_parts[2], "groups") == 0)
+ ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
+ else if (strcmp(hreq->path_parts[2], "items") == 0)
+ ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
- len = evbuffer_get_length(hreq->reply);
+ len = evbuffer_get_length(hreq->out_body);
switch (ret)
{
@@ -2027,16 +1989,15 @@ daap_reply_extra_data(struct httpd_request *hreq)
default:
if (len > 0)
- evbuffer_drain(hreq->reply, len);
+ evbuffer_drain(hreq->out_body, len);
goto no_artwork;
}
- headers = evhttp_request_get_output_headers(hreq->req);
- evhttp_remove_header(headers, "Content-Type");
- evhttp_add_header(headers, "Content-Type", ctype);
+ httpd_header_remove(hreq->out_headers, "Content-Type");
+ httpd_header_add(hreq->out_headers, "Content-Type", ctype);
snprintf(clen, sizeof(clen), "%ld", (long)len);
- evhttp_add_header(headers, "Content-Length", clen);
+ httpd_header_add(hreq->out_headers, "Content-Length", clen);
return DAAP_REPLY_OK_NO_GZIP;
@@ -2050,17 +2011,17 @@ daap_stream(struct httpd_request *hreq)
int id;
int ret;
- if (!hreq->req)
+ if (!hreq->backend)
{
DPRINTF(E_LOG, L_DAAP, "Bug! daap_stream() cannot be called without an actual connection\n");
return DAAP_REPLY_NO_CONNECTION;
}
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id);
+ ret = safe_atoi32(hreq->path_parts[3], &id);
if (ret < 0)
return DAAP_REPLY_BAD_REQUEST;
- httpd_stream_file(hreq->req, id);
+ httpd_stream_file(hreq, id);
return DAAP_REPLY_NONE;
}
@@ -2138,16 +2099,16 @@ daap_reply_dmap_test(struct httpd_request *hreq)
dmap_add_field(test, &dmap_TST8, buf, 0);
dmap_add_field(test, &dmap_TST9, buf, 0);
- dmap_add_container(hreq->reply, dmap_TEST.tag, evbuffer_get_length(test));
+ dmap_add_container(hreq->out_body, dmap_TEST.tag, evbuffer_get_length(test));
- ret = evbuffer_add_buffer(hreq->reply, test);
+ ret = evbuffer_add_buffer(hreq->out_body, test);
evbuffer_free(test);
if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Could not add test results to DMAP test reply\n");
- dmap_error_make(hreq->reply, dmap_TEST.tag, "Out of memory");
+ dmap_error_make(hreq->out_body, dmap_TEST.tag, "Out of memory");
return DAAP_REPLY_ERROR;
}
@@ -2175,7 +2136,7 @@ static struct httpd_uri_map daap_handlers[] =
},
{
.regexp = "^/update$",
- .handler = daap_reply_update
+ .handler = daap_reply_update,
},
{
.regexp = "^/activity$",
@@ -2237,11 +2198,9 @@ static struct httpd_uri_map daap_handlers[] =
* iTunes 12.1 gives us an absolute request-uri for streaming like
* http://10.1.1.20:3689/databases/1/items/1.mp3
*/
-void
-daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static void
+daap_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
- struct evkeyvalq *headers;
struct timespec start;
struct timespec end;
struct daap_session session;
@@ -2250,24 +2209,20 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
int ret;
int msec;
- DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", uri_parsed->uri);
-
- hreq = httpd_request_parse(req, uri_parsed, NULL, daap_handlers);
- if (!hreq)
+ if (!hreq->handler)
{
- DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", uri_parsed->path, uri_parsed->uri);
-
- httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ DPRINTF(E_LOG, L_DAAP, "Unrecognized path in DAAP request: '%s'\n", hreq->uri);
+ daap_reply_send(hreq, DAAP_REPLY_BAD_REQUEST);
return;
}
// Check if we have a session and point hreq->extra_data to it
- param = evhttp_find_header(hreq->query, "session-id");
+ param = httpd_query_value_find(hreq->query, "session-id");
if (param)
{
ret = safe_atoi32(param, &id);
if (ret < 0)
- DPRINTF(E_LOG, L_DAAP, "Ignoring non-numeric session id in DAAP request: '%s'\n", uri_parsed->uri);
+ DPRINTF(E_LOG, L_DAAP, "Ignoring non-numeric session id in DAAP request: '%s'\n", hreq->uri);
else
hreq->extra_data = daap_session_get(id);
}
@@ -2276,39 +2231,31 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
if (!hreq->extra_data)
{
memset(&session, 0, sizeof(struct daap_session));
- session.is_remote = (evhttp_find_header(hreq->query, "pairing-guid") != NULL);
+ session.is_remote = (httpd_query_value_find(hreq->query, "pairing-guid") != NULL);
hreq->extra_data = &session;
}
ret = daap_request_authorize(hreq);
if (ret < 0)
{
- free(hreq);
return;
}
// Set reply headers
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "Accept-Ranges", "bytes");
- evhttp_add_header(headers, "DAAP-Server", PACKAGE_NAME "/" VERSION);
+ httpd_header_add(hreq->out_headers, "Accept-Ranges", "bytes");
+ httpd_header_add(hreq->out_headers, "DAAP-Server", PACKAGE_NAME "/" VERSION);
// Content-Type for all replies, even the actual audio streaming. Note that
// video streaming will override this Content-Type with a more appropriate
// video/ Content-Type as expected by clients like Front Row.
- evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
-
- // Now we create the actual reply
- CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new());
+ httpd_header_add(hreq->out_headers, "Content-Type", "application/x-dmap-tagged");
// Try the cache
- ret = cache_daap_get(hreq->reply, uri_parsed->uri);
+ ret = cache_daap_get(hreq->out_body, hreq->uri);
if (ret == 0)
{
// The cache will return the data gzipped, so httpd_send_reply won't need to do it
- evhttp_add_header(headers, "Content-Encoding", "gzip");
- httpd_send_reply(req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply
-
- evbuffer_free(hreq->reply);
- free(hreq);
+ httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip");
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); // TODO not all want this reply
return;
}
@@ -2325,38 +2272,7 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec);
if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold() && hreq->user_agent)
- cache_daap_add(uri_parsed->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec);
-
- evbuffer_free(hreq->reply);
- free(hreq);
-}
-
-int
-daap_is_request(const char *path)
-{
- if (strncmp(path, "/databases/", strlen("/databases/")) == 0)
- return 1;
- if (strcmp(path, "/databases") == 0)
- return 1;
- if (strcmp(path, "/server-info") == 0)
- return 1;
- if (strcmp(path, "/content-codes") == 0)
- return 1;
- if (strcmp(path, "/login") == 0)
- return 1;
- if (strcmp(path, "/update") == 0)
- return 1;
- if (strcmp(path, "/activity") == 0)
- return 1;
- if (strcmp(path, "/logout") == 0)
- return 1;
-
-#ifdef DMAP_TEST
- if (strcmp(path, "/dmap-test") == 0)
- return 1;
-#endif
-
- return 0;
+ cache_daap_add(hreq->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec);
}
int
@@ -2377,24 +2293,24 @@ struct evbuffer *
daap_reply_build(const char *uri, const char *user_agent, int is_remote)
{
struct httpd_request *hreq;
- struct httpd_uri_parsed *uri_parsed;
- struct evbuffer *reply;
+ struct evbuffer *out_body = NULL;
struct daap_session session;
int ret;
DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri);
- reply = NULL;
-
- uri_parsed = httpd_uri_parse(uri);
- if (!uri_parsed)
- return NULL;
-
- hreq = httpd_request_parse(NULL, uri_parsed, user_agent, daap_handlers);
+ hreq = httpd_request_new(NULL, NULL, uri, user_agent);
if (!hreq)
{
- DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path '%s' in request: '%s'\n", uri_parsed->path, uri_parsed->uri);
- goto out_free_uri;
+ DPRINTF(E_LOG, L_DAAP, "Error building request: '%s'\n", uri);
+ goto out;
+ }
+
+ httpd_request_handler_set(hreq);
+ if (!hreq->handler)
+ {
+ DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path in request: '%s'\n", uri);
+ goto out;
}
memset(&session, 0, sizeof(struct daap_session));
@@ -2402,61 +2318,36 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote)
hreq->extra_data = &session;
- CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new());
-
ret = hreq->handler(hreq);
if (ret < 0)
{
- evbuffer_free(hreq->reply);
- goto out_free_hreq;
+ goto out;
}
- reply = hreq->reply;
+ // Take ownership of the reply
+ out_body = hreq->out_body;
+ hreq->out_body = NULL;
- out_free_hreq:
- free(hreq);
- out_free_uri:
- httpd_uri_free(uri_parsed);
+ out:
+ httpd_request_free(hreq);
- return reply;
+ return out_body;
}
-int
+static int
daap_init(void)
{
- char buf[64];
- int i;
- int ret;
-
srand((unsigned)time(NULL));
current_rev = 2;
- update_requests = NULL;
-
- for (i = 0; daap_handlers[i].handler; i++)
- {
- ret = regcomp(&daap_handlers[i].preg, daap_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &daap_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_DAAP, "DAAP init failed; regexp error: %s\n", buf);
- return -1;
- }
- }
return 0;
}
-void
+static void
daap_deinit(void)
{
struct daap_session *s;
struct daap_update_request *ur;
- struct evhttp_connection *evcon;
- int i;
-
- for (i = 0; daap_handlers[i].handler; i++)
- regfree(&daap_handlers[i].preg);
for (s = daap_sessions; daap_sessions; s = daap_sessions)
{
@@ -2468,13 +2359,24 @@ daap_deinit(void)
{
update_requests = ur->next;
- evcon = evhttp_request_get_connection(ur->req);
- if (evcon)
- {
- evhttp_connection_set_closecb(evcon, NULL, NULL);
- evhttp_connection_free(evcon);
- }
-
+ daap_reply_send(ur->hreq, DAAP_REPLY_SERVUNAVAIL);
update_free(ur);
}
}
+
+struct httpd_module httpd_daap =
+{
+ .name = "DAAP",
+ .type = MODULE_DAAP,
+ .logdomain = L_DAAP,
+ .subpaths = { "/databases/", NULL },
+#ifdef DMAP_TEST
+ .fullpaths = { "/databases", "/server-info", "/content-codes", "/login", "/update", "/activity", "/logout", "/dmap-test", NULL },
+#else
+ .fullpaths = { "/databases", "/server-info", "/content-codes", "/login", "/update", "/activity", "/logout", NULL },
+#endif
+ .handlers = daap_handlers,
+ .init = daap_init,
+ .deinit = daap_deinit,
+ .request = daap_request,
+};
diff --git a/src/httpd_daap.h b/src/httpd_daap.h
index 53fb6e55..2efd4b42 100644
--- a/src/httpd_daap.h
+++ b/src/httpd_daap.h
@@ -2,20 +2,6 @@
#ifndef __HTTPD_DAAP_H__
#define __HTTPD_DAAP_H__
-#include "httpd.h"
-
-int
-daap_init(void);
-
-void
-daap_deinit(void);
-
-void
-daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-daap_is_request(const char *path);
-
int
daap_session_is_valid(int id);
diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c
index bee56149..f8ec8c9d 100644
--- a/src/httpd_dacp.c
+++ b/src/httpd_dacp.c
@@ -26,19 +26,14 @@
#include
#include
#include
-#include
#include
#include
#include
-#ifdef HAVE_EVENTFD
-# include
-#endif
-
+#include
#include
-#include
-#include "httpd_dacp.h"
+#include "httpd_internal.h"
#include "httpd_daap.h"
#include "logger.h"
#include "misc.h"
@@ -51,11 +46,9 @@
#define DACP_VOLUME_STEP 5
-/* httpd event base, from httpd.c */
-extern struct event_base *evbase_httpd;
-
struct dacp_update_request {
- struct evhttp_request *req;
+ struct httpd_request *hreq;
+ struct event *updateev;
struct dacp_update_request *next;
};
@@ -130,31 +123,30 @@ dacp_propset_userrating(const char *value, struct httpd_request *hreq);
/* gperf static hash, dacp_prop.gperf */
#include "dacp_prop_hash.h"
-
-/* Play status update */
-#ifdef HAVE_EVENTFD
-static int update_efd;
-#else
-static int update_pipe[2];
-#endif
-static struct event *updateev;
-/* Next revision number the client should call with */
-static int current_rev;
-
-/* Play status update requests */
+// Play status update requests
static struct dacp_update_request *update_requests;
+static pthread_mutex_t update_request_lck;
+// Next revision number the client should call with
+static int update_current_rev;
-/* Seek timer */
-static struct event *seek_timer;
-static int seek_target;
-
-/* If an item is removed from the library while in the queue, we replace it with this */
+// If an item is removed from the library while in the queue, we replace it with this
static struct media_file_info dummy_mfi;
static struct db_queue_item dummy_queue_item;
/* -------------------------------- HELPERS --------------------------------- */
+static void
+dacp_send_error(struct httpd_request *hreq, const char *container, const char *errmsg)
+{
+ if (!hreq)
+ return;
+
+ dmap_error_make(hreq->out_body, container, errmsg);
+
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
+}
+
static void
dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
{
@@ -542,15 +534,13 @@ speaker_enum_cb(struct player_speaker_info *spk, void *arg)
static int
speaker_get(struct player_speaker_info *speaker_info, struct httpd_request *hreq, const char *req_name)
{
- struct evkeyvalq *headers;
const char *remote;
uint32_t active_remote;
int ret;
- headers = evhttp_request_get_input_headers(hreq->req);
- remote = evhttp_find_header(headers, "Active-Remote");
+ remote = httpd_header_find(hreq->in_headers, "Active-Remote");
- if (!headers || !remote || (safe_atou32(remote, &active_remote) < 0))
+ if (!remote || (safe_atou32(remote, &active_remote) < 0))
{
DPRINTF(E_LOG, L_DACP, "'%s' request from '%s' has invalid Active-Remote: '%s'\n", req_name, hreq->peer_address, remote);
return -1;
@@ -583,6 +573,8 @@ speaker_volume_step(struct player_speaker_info *speaker_info, int step)
static void
seek_timer_cb(int fd, short what, void *arg)
{
+ intptr_t seek_target_packed = (intptr_t)arg;
+ int seek_target = seek_target_packed;
int ret;
DPRINTF(E_DBG, L_DACP, "Seek timer expired, target %d ms\n", seek_target);
@@ -610,7 +602,7 @@ dacp_request_authorize(struct httpd_request *hreq)
if (net_peer_address_is_trusted(hreq->peer_address))
return 0;
- param = evhttp_find_header(hreq->query, "session-id");
+ param = httpd_query_value_find(hreq->query, "session-id");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "No session-id specified in request\n");
@@ -633,9 +625,9 @@ dacp_request_authorize(struct httpd_request *hreq)
return 0;
invalid:
- DPRINTF(E_LOG, L_DACP, "Unauthorized request '%s' from '%s' (is peer trusted in your config?)\n", hreq->uri_parsed->uri, hreq->peer_address);
+ DPRINTF(E_LOG, L_DACP, "Unauthorized request '%s' from '%s' (is peer trusted in your config?)\n", hreq->uri, hreq->peer_address);
- httpd_send_error(hreq->req, 403, "Forbidden");
+ httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden");
return -1;
}
@@ -643,7 +635,7 @@ dacp_request_authorize(struct httpd_request *hreq)
/* ---------------------- UPDATE REQUESTS HANDLERS -------------------------- */
static int
-make_playstatusupdate(struct evbuffer *evbuf)
+make_playstatusupdate(struct evbuffer *evbuf, int current_rev)
{
struct player_status status;
struct db_queue_item *queue_item = NULL;
@@ -709,115 +701,42 @@ make_playstatusupdate(struct evbuffer *evbuf)
}
static void
-playstatusupdate_cb(int fd, short what, void *arg)
+playstatusupdate_cb(int fd, short what, void *arg);
+
+static struct dacp_update_request *
+update_request_new(struct httpd_request *hreq)
{
struct dacp_update_request *ur;
- struct evbuffer *evbuf;
- struct evbuffer *update;
- struct evhttp_connection *evcon;
- uint8_t *buf;
- size_t len;
- int ret;
-#ifdef HAVE_EVENTFD
- eventfd_t count;
+ CHECK_NULL(L_DACP, ur = calloc(1, sizeof(struct dacp_update_request)));
+ CHECK_NULL(L_DACP, ur->updateev = event_new(hreq->evbase, -1, 0, playstatusupdate_cb, ur));
+ ur->hreq = hreq;
- ret = eventfd_read(update_efd, &count);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_DACP, "Could not read playstatusupdate event counter: %s\n", strerror(errno));
-
- goto readd;
- }
-#else
- int dummy;
-
- read(update_pipe[0], &dummy, sizeof(dummy));
-#endif
-
- current_rev++;
-
- if (!update_requests)
- goto readd;
-
- CHECK_NULL(L_DACP, evbuf = evbuffer_new());
- CHECK_NULL(L_DACP, update = evbuffer_new());
-
- ret = make_playstatusupdate(update);
- if (ret < 0)
- goto out_free_update;
-
- len = evbuffer_get_length(update);
-
- for (ur = update_requests; update_requests; ur = update_requests)
- {
- update_requests = ur->next;
-
- evcon = evhttp_request_get_connection(ur->req);
- if (evcon)
- evhttp_connection_set_closecb(evcon, NULL, NULL);
-
- // Only copy buffer if we actually need to reuse it
- if (ur->next)
- {
- buf = evbuffer_pullup(update, -1);
- evbuffer_add(evbuf, buf, len);
- httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf, 0);
- }
- else
- httpd_send_reply(ur->req, HTTP_OK, "OK", update, 0);
-
- free(ur);
- }
-
- out_free_update:
- evbuffer_free(update);
- evbuffer_free(evbuf);
- readd:
- ret = event_add(updateev, NULL);
- if (ret < 0)
- DPRINTF(E_LOG, L_DACP, "Couldn't re-add event for playstatusupdate\n");
-}
-
-/* Thread: player */
-static void
-dacp_playstatus_update_handler(short event_mask)
-{
- int ret;
-
-#ifdef HAVE_EVENTFD
- ret = eventfd_write(update_efd, 1);
- if (ret < 0)
- DPRINTF(E_LOG, L_DACP, "Could not send status update event: %s\n", strerror(errno));
-#else
- int dummy = 42;
-
- ret = write(update_pipe[1], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- DPRINTF(E_LOG, L_DACP, "Could not write to status update fd: %s\n", strerror(errno));
-#endif
+ return ur;
}
static void
-update_fail_cb(struct evhttp_connection *evcon, void *arg)
+update_request_free(struct dacp_update_request *ur)
+{
+ if (!ur)
+ return;
+
+ if (ur->updateev)
+ event_free(ur->updateev);
+
+ free(ur);
+}
+
+static void
+update_request_remove(struct dacp_update_request **head, struct dacp_update_request *ur)
{
- struct dacp_update_request *ur;
struct dacp_update_request *p;
- struct evhttp_connection *evc;
- ur = (struct dacp_update_request *)arg;
-
- DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n");
-
- evc = evhttp_request_get_connection(ur->req);
- if (evc)
- evhttp_connection_set_closecb(evc, NULL, NULL);
-
- if (ur == update_requests)
- update_requests = ur->next;
+ if (ur == *head)
+ *head = ur->next;
else
{
- for (p = update_requests; p && (p->next != ur); p = p->next)
+ for (p = *head; p && (p->next != ur); p = p->next)
;
if (!p)
@@ -829,8 +748,55 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg)
p->next = ur->next;
}
- evhttp_request_free(ur->req);
- free(ur);
+ update_request_free(ur);
+}
+
+static void
+playstatusupdate_cb(int fd, short what, void *arg)
+{
+ struct dacp_update_request *ur = arg;
+ struct httpd_request *hreq = ur->hreq;
+ int ret;
+
+ ret = make_playstatusupdate(hreq->out_body, update_current_rev);
+ if (ret < 0)
+ goto error;
+
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
+
+ pthread_mutex_lock(&update_request_lck);
+ update_request_remove(&update_requests, ur);
+ pthread_mutex_unlock(&update_request_lck);
+
+ error:
+ return;
+}
+
+static void
+update_fail_cb(void *arg)
+{
+ struct dacp_update_request *ur = arg;
+
+ DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n");
+
+ pthread_mutex_lock(&update_request_lck);
+ update_request_remove(&update_requests, ur);
+ pthread_mutex_unlock(&update_request_lck);
+}
+
+/* Thread: player */
+static void
+dacp_playstatus_update_handler(short event_mask)
+{
+ struct dacp_update_request *ur;
+
+ pthread_mutex_lock(&update_request_lck);
+ update_current_rev++;
+ for (ur = update_requests; ur; ur = ur->next)
+ {
+ event_active(ur->updateev, 0, 0);
+ }
+ pthread_mutex_unlock(&update_request_lck);
}
@@ -955,7 +921,7 @@ dacp_propset_volume(const char *value, struct httpd_request *hreq)
return;
}
- param = evhttp_find_header(hreq->query, "speaker-id");
+ param = httpd_query_value_find(hreq->query, "speaker-id");
if (param)
{
ret = safe_atou64(param, &id);
@@ -970,7 +936,7 @@ dacp_propset_volume(const char *value, struct httpd_request *hreq)
return;
}
- param = evhttp_find_header(hreq->query, "include-speaker-id");
+ param = httpd_query_value_find(hreq->query, "include-speaker-id");
if (param)
{
ret = safe_atou64(param, &id);
@@ -1037,6 +1003,8 @@ static void
dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
{
struct timeval tv;
+ int seek_target;
+ intptr_t seek_target_packed;
int ret;
ret = safe_atoi32(value, &seek_target);
@@ -1047,9 +1015,12 @@ dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
return;
}
+ seek_target_packed = seek_target;
+
evutil_timerclear(&tv);
tv.tv_usec = 200 * 1000;
- evtimer_add(seek_timer, &tv);
+
+ event_base_once(hreq->evbase, -1, EV_TIMEOUT, seek_timer_cb, (void *)seek_target_packed, &tv);
}
static void
@@ -1102,9 +1073,9 @@ dacp_propset_userrating(const char *value, struct httpd_request *hreq)
return;
}
- param = evhttp_find_header(hreq->query, "item-spec"); // Remote
+ param = httpd_query_value_find(hreq->query, "item-spec"); // Remote
if (!param)
- param = evhttp_find_header(hreq->query, "song-spec"); // Retune
+ param = httpd_query_value_find(hreq->query, "song-spec"); // Retune
if (!param)
{
@@ -1169,33 +1140,33 @@ static int
dacp_reply_ctrlint(struct httpd_request *hreq)
{
/* /ctrl-int */
- CHECK_ERR(L_DACP, evbuffer_expand(hreq->reply, 256));
+ CHECK_ERR(L_DACP, evbuffer_expand(hreq->out_body, 256));
/* If tags are added or removed container sizes should be adjusted too */
- dmap_add_container(hreq->reply, "caci", 194); /* 8, unknown dacp container - size of content */
- dmap_add_int(hreq->reply, "mstt", 200); /* 12, dmap.status */
- dmap_add_char(hreq->reply, "muty", 0); /* 9, dmap.updatetype */
- dmap_add_int(hreq->reply, "mtco", 1); /* 12, dmap.specifiedtotalcount */
- dmap_add_int(hreq->reply, "mrco", 1); /* 12, dmap.returnedcount */
- dmap_add_container(hreq->reply, "mlcl", 141); /* 8, dmap.listing - size of content */
- dmap_add_container(hreq->reply, "mlit", 133); /* 8, dmap.listingitem - size of content */
- dmap_add_int(hreq->reply, "miid", 1); /* 12, dmap.itemid - database ID */
- dmap_add_char(hreq->reply, "cmik", 1); /* 9, unknown */
+ dmap_add_container(hreq->out_body, "caci", 194); /* 8, unknown dacp container - size of content */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12, dmap.status */
+ dmap_add_char(hreq->out_body, "muty", 0); /* 9, dmap.updatetype */
+ dmap_add_int(hreq->out_body, "mtco", 1); /* 12, dmap.specifiedtotalcount */
+ dmap_add_int(hreq->out_body, "mrco", 1); /* 12, dmap.returnedcount */
+ dmap_add_container(hreq->out_body, "mlcl", 141); /* 8, dmap.listing - size of content */
+ dmap_add_container(hreq->out_body, "mlit", 133); /* 8, dmap.listingitem - size of content */
+ dmap_add_int(hreq->out_body, "miid", 1); /* 12, dmap.itemid - database ID */
+ dmap_add_char(hreq->out_body, "cmik", 1); /* 9, unknown */
- dmap_add_int(hreq->reply, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */
- dmap_add_int(hreq->reply, "capr", (2 << 16 | 5)); /* 12, dacp.protocolversion */
+ dmap_add_int(hreq->out_body, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */
+ dmap_add_int(hreq->out_body, "capr", (2 << 16 | 5)); /* 12, dacp.protocolversion */
- dmap_add_char(hreq->reply, "cmsp", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "aeFR", 0x64); /* 9, unknown */
- dmap_add_char(hreq->reply, "cmsv", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "cass", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "caov", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "casu", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "ceSG", 1); /* 9, unknown */
- dmap_add_char(hreq->reply, "cmrl", 1); /* 9, unknown */
- dmap_add_long(hreq->reply, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */
+ dmap_add_char(hreq->out_body, "cmsp", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "aeFR", 0x64); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "cmsv", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "cass", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "caov", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "casu", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "ceSG", 1); /* 9, unknown */
+ dmap_add_char(hreq->out_body, "cmrl", 1); /* 9, unknown */
+ dmap_add_long(hreq->out_body, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
}
@@ -1215,7 +1186,7 @@ dacp_reply_cue_play(struct httpd_request *hreq)
/* /cue?command=play&query=...&sort=...&index=N */
- param = evhttp_find_header(hreq->query, "clear-first");
+ param = httpd_query_value_find(hreq->query, "clear-first");
if (param)
{
ret = safe_atoi32(param, &clear);
@@ -1231,17 +1202,17 @@ dacp_reply_cue_play(struct httpd_request *hreq)
player_get_status(&status);
- cuequery = evhttp_find_header(hreq->query, "query");
+ cuequery = httpd_query_value_find(hreq->query, "query");
if (cuequery)
{
- sort = evhttp_find_header(hreq->query, "sort");
+ sort = httpd_query_value_find(hreq->query, "sort");
ret = dacp_queueitem_add(cuequery, NULL, sort, 0, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Could not build song queue\n");
- dmap_send_error(hreq->req, "cacr", "Could not build song queue");
+ dacp_send_error(hreq, "cacr", "Could not build song queue");
return -1;
}
}
@@ -1250,12 +1221,12 @@ dacp_reply_cue_play(struct httpd_request *hreq)
player_playback_stop();
}
- param = evhttp_find_header(hreq->query, "dacp.shufflestate");
+ param = httpd_query_value_find(hreq->query, "dacp.shufflestate");
if (param)
dacp_propset_shufflestate(param, NULL);
pos = 0;
- param = evhttp_find_header(hreq->query, "index");
+ param = httpd_query_value_find(hreq->query, "index");
if (param)
{
ret = safe_atou32(param, &pos);
@@ -1264,10 +1235,10 @@ dacp_reply_cue_play(struct httpd_request *hreq)
}
/* If selection was from Up Next queue or history queue (command will be playnow), then index is relative */
- if ((param = evhttp_find_header(hreq->query, "command")) && (strcmp(param, "playnow") == 0))
+ if ((param = httpd_query_value_find(hreq->query, "command")) && (strcmp(param, "playnow") == 0))
{
/* If mode parameter is -1 or 1, the index is relative to the history queue, otherwise to the Up Next queue */
- param = evhttp_find_header(hreq->query, "mode");
+ param = httpd_query_value_find(hreq->query, "mode");
if (param && ((strcmp(param, "-1") == 0) || (strcmp(param, "1") == 0)))
{
/* Play from history queue */
@@ -1281,7 +1252,7 @@ dacp_reply_cue_play(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n");
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
}
@@ -1289,7 +1260,7 @@ dacp_reply_cue_play(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n");
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
}
@@ -1304,7 +1275,7 @@ dacp_reply_cue_play(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d, now playing=%d\n", pos, status.item_id);
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
}
@@ -1316,7 +1287,7 @@ dacp_reply_cue_play(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d\n", pos);
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
}
@@ -1327,19 +1298,19 @@ dacp_reply_cue_play(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
player_get_status(&status);
- CHECK_ERR(L_DACP, evbuffer_expand(hreq->reply, 64));
+ CHECK_ERR(L_DACP, evbuffer_expand(hreq->out_body, 64));
- dmap_add_container(hreq->reply, "cacr", 24); /* 8 + len */
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "miid", status.id);/* 12 */
+ dmap_add_container(hreq->out_body, "cacr", 24); /* 8 + len */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "miid", status.id);/* 12 */
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
}
@@ -1353,13 +1324,13 @@ dacp_reply_cue_clear(struct httpd_request *hreq)
db_queue_clear(0);
- CHECK_ERR(L_DACP, evbuffer_expand(hreq->reply, 64));
+ CHECK_ERR(L_DACP, evbuffer_expand(hreq->out_body, 64));
- dmap_add_container(hreq->reply, "cacr", 24); /* 8 + len */
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "miid", 0); /* 12 */
+ dmap_add_container(hreq->out_body, "cacr", 24); /* 8 + len */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "miid", 0); /* 12 */
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
}
@@ -1374,12 +1345,12 @@ dacp_reply_cue(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "command");
+ param = httpd_query_value_find(hreq->query, "command");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "No command in cue request\n");
- dmap_send_error(hreq->req, "cacr", "No command in cue request");
+ dacp_send_error(hreq, "cacr", "No command in cue request");
return -1;
}
@@ -1391,7 +1362,7 @@ dacp_reply_cue(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Unknown cue command %s\n", param);
- dmap_send_error(hreq->req, "cacr", "Unknown command in cue request");
+ dacp_send_error(hreq, "cacr", "Unknown command in cue request");
return -1;
}
}
@@ -1408,12 +1379,12 @@ dacp_reply_play(struct httpd_request *hreq)
ret = player_playback_start();
if (ret < 0)
{
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1440,10 +1411,10 @@ dacp_reply_playspec(struct httpd_request *hreq)
return -1;
/* Check for shuffle */
- shuffle = evhttp_find_header(hreq->query, "dacp.shufflestate");
+ shuffle = httpd_query_value_find(hreq->query, "dacp.shufflestate");
/* Playlist ID */
- param = evhttp_find_header(hreq->query, "container-spec");
+ param = httpd_query_value_find(hreq->query, "container-spec");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "No container-spec in playspec request\n");
@@ -1472,9 +1443,9 @@ dacp_reply_playspec(struct httpd_request *hreq)
if (!shuffle)
{
/* Start song ID */
- if ((param = evhttp_find_header(hreq->query, "item-spec")))
+ if ((param = httpd_query_value_find(hreq->query, "item-spec")))
plid = 0; // This is a podcast/audiobook - just play a single item, not a playlist
- else if (!(param = evhttp_find_header(hreq->query, "container-item-spec")))
+ else if (!(param = httpd_query_value_find(hreq->query, "container-item-spec")))
{
DPRINTF(E_LOG, L_DACP, "No container-item-spec/item-spec in playspec request\n");
goto out_fail;
@@ -1545,11 +1516,11 @@ dacp_reply_playspec(struct httpd_request *hreq)
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
out_fail:
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
@@ -1566,7 +1537,7 @@ dacp_reply_stop(struct httpd_request *hreq)
player_playback_stop();
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1583,7 +1554,7 @@ dacp_reply_pause(struct httpd_request *hreq)
player_playback_pause();
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1610,13 +1581,13 @@ dacp_reply_playpause(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after pause\n");
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1635,7 +1606,7 @@ dacp_reply_nextitem(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for nextitem\n");
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
@@ -1644,12 +1615,12 @@ dacp_reply_nextitem(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after nextitem\n");
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1668,7 +1639,7 @@ dacp_reply_previtem(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for previtem\n");
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
@@ -1677,12 +1648,12 @@ dacp_reply_previtem(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after previtem\n");
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1699,7 +1670,7 @@ dacp_reply_beginff(struct httpd_request *hreq)
/* TODO */
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1716,7 +1687,7 @@ dacp_reply_beginrew(struct httpd_request *hreq)
/* TODO */
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1733,7 +1704,7 @@ dacp_reply_playresume(struct httpd_request *hreq)
/* TODO */
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1764,7 +1735,7 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq)
DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n");
span = 50; /* Default */
- param = evhttp_find_header(hreq->query, "span");
+ param = httpd_query_value_find(hreq->query, "span");
if (param)
{
ret = safe_atoi32(param, &span);
@@ -1773,7 +1744,7 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq)
}
CHECK_NULL(L_DACP, songlist = evbuffer_new());
- CHECK_ERR(L_DACP, evbuffer_expand(hreq->reply, 128));
+ CHECK_ERR(L_DACP, evbuffer_expand(hreq->out_body, 128));
player_get_status(&status);
@@ -1868,24 +1839,24 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq)
/* Final construction of reply */
playlist_length = evbuffer_get_length(playlists);
- dmap_add_container(hreq->reply, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */
- dmap_add_int(hreq->reply, "mstt", 200); /* 12, dmap.status */
- dmap_add_int(hreq->reply, "mtco", abs(span)); /* 12 */
- dmap_add_int(hreq->reply, "mrco", count); /* 12 */
- dmap_add_char(hreq->reply, "ceQu", 0); /* 9 */
- dmap_add_container(hreq->reply, "mlcl", 8 + playlist_length + songlist_length); /* 8 */
- dmap_add_container(hreq->reply, "ceQS", playlist_length); /* 8 */
+ dmap_add_container(hreq->out_body, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12, dmap.status */
+ dmap_add_int(hreq->out_body, "mtco", abs(span)); /* 12 */
+ dmap_add_int(hreq->out_body, "mrco", count); /* 12 */
+ dmap_add_char(hreq->out_body, "ceQu", 0); /* 9 */
+ dmap_add_container(hreq->out_body, "mlcl", 8 + playlist_length + songlist_length); /* 8 */
+ dmap_add_container(hreq->out_body, "ceQS", playlist_length); /* 8 */
- CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->reply, playlists));
- CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->reply, songlist));
+ CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->out_body, playlists));
+ CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->out_body, songlist));
evbuffer_free(playlists);
evbuffer_free(songlist);
- dmap_add_char(hreq->reply, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */
- dmap_add_char(hreq->reply, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */
+ dmap_add_char(hreq->out_body, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */
+ dmap_add_char(hreq->out_body, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
@@ -1893,7 +1864,7 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq)
DPRINTF(E_LOG, L_DACP, "Database error in dacp_reply_playqueuecontents\n");
evbuffer_free(songlist);
- dmap_send_error(hreq->req, "ceQR", "Database error");
+ dacp_send_error(hreq, "ceQR", "Database error");
return -1;
}
@@ -1904,7 +1875,7 @@ dacp_reply_playqueueedit_clear(struct httpd_request *hreq)
const char *param;
struct player_status status;
- param = evhttp_find_header(hreq->query, "mode");
+ param = httpd_query_value_find(hreq->query, "mode");
/*
* The mode parameter contains the playlist to be cleared.
@@ -1919,11 +1890,11 @@ dacp_reply_playqueueedit_clear(struct httpd_request *hreq)
db_queue_clear(status.item_id);
}
- dmap_add_container(hreq->reply, "cacr", 24); /* 8 + len */
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
- dmap_add_int(hreq->reply, "miid", 0); /* 12 */
+ dmap_add_container(hreq->out_body, "cacr", 24); /* 8 + len */
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
+ dmap_add_int(hreq->out_body, "miid", 0); /* 12 */
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
}
@@ -1955,7 +1926,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
mode = 1;
- param = evhttp_find_header(hreq->query, "mode");
+ param = httpd_query_value_find(hreq->query, "mode");
if (param)
{
ret = safe_atoi32(param, &mode);
@@ -1963,7 +1934,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
}
@@ -1977,16 +1948,16 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
if (mode == 2)
player_shuffle_set(1);
- editquery = evhttp_find_header(hreq->query, "query");
+ editquery = httpd_query_value_find(hreq->query, "query");
if (!editquery)
{
DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
- sort = evhttp_find_header(hreq->query, "sort");
+ sort = httpd_query_value_find(hreq->query, "sort");
// if sort param is missing and an album or artist is added to the queue, set sort to "album"
if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:")))
@@ -1995,9 +1966,9 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
}
// only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next)
- queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(hreq->query, "queuefilter");
+ queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : httpd_query_value_find(hreq->query, "queuefilter");
- querymodifier = evhttp_find_header(hreq->query, "query-modifier");
+ querymodifier = httpd_query_value_find(hreq->query, "query-modifier");
if (!querymodifier || (strcmp(querymodifier, "containers") != 0))
{
quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)"));
@@ -2011,7 +1982,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery);
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
@@ -2023,7 +1994,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not build song queue\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
@@ -2056,12 +2027,12 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
- dmap_send_error(hreq->req, "cacr", "Playback failed to start");
+ dacp_send_error(hreq, "cacr", "Playback failed to start");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2083,7 +2054,7 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq)
int src;
int dst;
- param = evhttp_find_header(hreq->query, "edit-params");
+ param = httpd_query_value_find(hreq->query, "edit-params");
if (param)
{
ret = safe_atoi32(strchr(param, ':') + 1, &src);
@@ -2091,7 +2062,7 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-from value in playqueue-edit request\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
@@ -2100,7 +2071,7 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-to value in playqueue-edit request\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
@@ -2109,7 +2080,7 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq)
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2127,7 +2098,7 @@ dacp_reply_playqueueedit_remove(struct httpd_request *hreq)
int item_index;
int ret;
- param = evhttp_find_header(hreq->query, "items");
+ param = httpd_query_value_find(hreq->query, "items");
if (param)
{
ret = safe_atoi32(param, &item_index);
@@ -2135,7 +2106,7 @@ dacp_reply_playqueueedit_remove(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Invalid edit-params remove item value in playqueue-edit request\n");
- dmap_send_error(hreq->req, "cacr", "Invalid request");
+ dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
@@ -2145,7 +2116,7 @@ dacp_reply_playqueueedit_remove(struct httpd_request *hreq)
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2203,12 +2174,12 @@ dacp_reply_playqueueedit(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "command");
+ param = httpd_query_value_find(hreq->query, "command");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n");
- dmap_send_error(hreq->req, "cmst", "Invalid request");
+ dacp_send_error(hreq, "cmst", "Invalid request");
return -1;
}
@@ -2226,7 +2197,7 @@ dacp_reply_playqueueedit(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param);
- dmap_send_error(hreq->req, "cmst", "Invalid request");
+ dacp_send_error(hreq, "cmst", "Invalid request");
return -1;
}
}
@@ -2235,8 +2206,6 @@ static int
dacp_reply_playstatusupdate(struct httpd_request *hreq)
{
struct dacp_update_request *ur;
- struct evhttp_connection *evcon;
- struct bufferevent *bufev;
const char *param;
int reqd_rev;
int ret;
@@ -2245,12 +2214,12 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "revision-number");
+ param = httpd_query_value_find(hreq->query, "revision-number");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "Missing revision-number in update request\n");
- dmap_send_error(hreq->req, "cmst", "Invalid request");
+ dacp_send_error(hreq, "cmst", "Invalid request");
return -1;
}
@@ -2259,56 +2228,42 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Parameter revision-number not an integer\n");
- dmap_send_error(hreq->req, "cmst", "Invalid request");
+ dacp_send_error(hreq, "cmst", "Invalid request");
return -1;
}
// Caller didn't use current revision number. It was probably his first
// request so we will give him status immediately, incl. which revision number
// to use when he calls again.
- if (reqd_rev != current_rev)
+ if (reqd_rev != update_current_rev)
{
- ret = make_playstatusupdate(hreq->reply);
+ ret = make_playstatusupdate(hreq->out_body, update_current_rev);
if (ret < 0)
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
else
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return ret;
}
// Else, just let the request hang until we have changes to push back
- ur = calloc(1, sizeof(struct dacp_update_request));
+ ur = update_request_new(hreq);
if (!ur)
{
DPRINTF(E_LOG, L_DACP, "Out of memory for update request\n");
- dmap_send_error(hreq->req, "cmst", "Out of memory");
+ dacp_send_error(hreq, "cmst", "Out of memory");
return -1;
}
- ur->req = hreq->req;
-
+ pthread_mutex_lock(&update_request_lck);
ur->next = update_requests;
update_requests = ur;
+ pthread_mutex_unlock(&update_request_lck);
- /* If the connection fails before we have an update to push out
- * to the client, we need to know.
- */
- evcon = evhttp_request_get_connection(hreq->req);
- if (evcon)
- {
- evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
-
- // This is a workaround for some versions of libevent (2.0, but possibly
- // also 2.1) that don't detect if the client hangs up, and thus don't
- // clean up and never call update_fail_cb(). See github issue #870 and
- // https://github.com/libevent/libevent/issues/666. It should probably be
- // removed again in the future. The workaround is also present in daap.c
- bufev = evhttp_connection_get_bufferevent(evcon);
- if (bufev)
- bufferevent_enable(bufev, EV_READ);
- }
+ // If the connection fails before we have an update to push out to the client,
+ // we need to know.
+ httpd_request_close_cb_set(hreq, update_fail_cb, ur);
return 0;
}
@@ -2317,7 +2272,6 @@ static int
dacp_reply_nowplayingartwork(struct httpd_request *hreq)
{
char clen[32];
- struct evkeyvalq *headers;
const char *param;
char *ctype;
size_t len;
@@ -2330,7 +2284,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "mw");
+ param = httpd_query_value_find(hreq->query, "mw");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "Request for artwork without mw parameter\n");
@@ -2344,7 +2298,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
goto error;
}
- param = evhttp_find_header(hreq->query, "mh");
+ param = httpd_query_value_find(hreq->query, "mh");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "Request for artwork without mh parameter\n");
@@ -2362,8 +2316,8 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
if (ret < 0)
goto no_artwork;
- ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
- len = evbuffer_get_length(hreq->reply);
+ ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
+ len = evbuffer_get_length(hreq->out_body);
switch (ret)
{
@@ -2377,26 +2331,25 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
default:
if (len > 0)
- evbuffer_drain(hreq->reply, len);
+ evbuffer_drain(hreq->out_body, len);
goto no_artwork;
}
- headers = evhttp_request_get_output_headers(hreq->req);
- evhttp_remove_header(headers, "Content-Type");
- evhttp_add_header(headers, "Content-Type", ctype);
+ httpd_header_remove(hreq->out_headers, "Content-Type");
+ httpd_header_add(hreq->out_headers, "Content-Type", ctype);
snprintf(clen, sizeof(clen), "%ld", (long)len);
- evhttp_add_header(headers, "Content-Length", clen);
+ httpd_header_add(hreq->out_headers, "Content-Length", clen);
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
return 0;
no_artwork:
- httpd_send_error(hreq->req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return 0;
error:
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
@@ -2418,12 +2371,12 @@ dacp_reply_getproperty(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "properties");
+ param = httpd_query_value_find(hreq->query, "properties");
if (!param)
{
DPRINTF(E_WARN, L_DACP, "Invalid DACP getproperty request, no properties\n");
- dmap_send_error(hreq->req, "cmgt", "Invalid request");
+ dacp_send_error(hreq, "cmgt", "Invalid request");
return -1;
}
@@ -2432,7 +2385,7 @@ dacp_reply_getproperty(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not duplicate properties parameter; out of memory\n");
- dmap_send_error(hreq->req, "cmgt", "Out of memory");
+ dacp_send_error(hreq, "cmgt", "Out of memory");
return -1;
}
@@ -2441,7 +2394,7 @@ dacp_reply_getproperty(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for properties list\n");
- dmap_send_error(hreq->req, "cmgt", "Out of memory");
+ dacp_send_error(hreq, "cmgt", "Out of memory");
goto out_free_propstr;
}
@@ -2454,7 +2407,7 @@ dacp_reply_getproperty(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_DACP, "Could not fetch queue_item for item-id %d\n", status.item_id);
- dmap_send_error(hreq->req, "cmgt", "Server error");
+ dacp_send_error(hreq, "cmgt", "Server error");
goto out_free_proplist;
}
}
@@ -2482,14 +2435,14 @@ dacp_reply_getproperty(struct httpd_request *hreq)
free_queue_item(queue_item, 0);
len = evbuffer_get_length(proplist);
- dmap_add_container(hreq->reply, "cmgt", 12 + len);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
+ dmap_add_container(hreq->out_body, "cmgt", 12 + len);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
- CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->reply, proplist));
+ CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->out_body, proplist));
evbuffer_free(proplist);
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
@@ -2502,11 +2455,27 @@ dacp_reply_getproperty(struct httpd_request *hreq)
return -1;
}
+static void
+setproperty_cb(const char *key, const char *val, void *arg)
+{
+ struct httpd_request *hreq = arg;
+ const struct dacp_prop_map *dpm = dacp_find_prop(key, strlen(key));
+
+ if (!dpm)
+ {
+ DPRINTF(E_SPAM, L_DACP, "Unknown DACP property %s\n", key);
+ return;
+ }
+
+ if (dpm->propset)
+ dpm->propset(val, hreq);
+ else
+ DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
+}
+
static int
dacp_reply_setproperty(struct httpd_request *hreq)
{
- const struct dacp_prop_map *dpm;
- struct evkeyval *param;
int ret;
ret = dacp_request_authorize(hreq);
@@ -2525,24 +2494,10 @@ dacp_reply_setproperty(struct httpd_request *hreq)
/* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */
- TAILQ_FOREACH(param, hreq->query, next)
- {
- dpm = dacp_find_prop(param->key, strlen(param->key));
-
- if (!dpm)
- {
- DPRINTF(E_SPAM, L_DACP, "Unknown DACP property %s\n", param->key);
- continue;
- }
-
- if (dpm->propset)
- dpm->propset(param->value, hreq);
- else
- DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
- }
+ httpd_query_iterate(hreq->query, setproperty_cb, hreq);
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2563,14 +2518,14 @@ dacp_reply_getspeakers(struct httpd_request *hreq)
player_speaker_enumerate(speaker_enum_cb, spklist);
len = evbuffer_get_length(spklist);
- dmap_add_container(hreq->reply, "casp", 12 + len);
- dmap_add_int(hreq->reply, "mstt", 200); /* 12 */
+ dmap_add_container(hreq->out_body, "casp", 12 + len);
+ dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */
- evbuffer_add_buffer(hreq->reply, spklist);
+ evbuffer_add_buffer(hreq->out_body, spklist);
evbuffer_free(spklist);
- httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
return 0;
}
@@ -2589,12 +2544,12 @@ dacp_reply_setspeakers(struct httpd_request *hreq)
if (ret < 0)
return -1;
- param = evhttp_find_header(hreq->query, "speaker-id");
+ param = httpd_query_value_find(hreq->query, "speaker-id");
if (!param)
{
DPRINTF(E_LOG, L_DACP, "Missing speaker-id parameter in DACP setspeakers request\n");
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
@@ -2652,15 +2607,15 @@ dacp_reply_setspeakers(struct httpd_request *hreq)
/* Password problem */
if (ret == -2)
- httpd_send_error(hreq->req, 902, "");
+ httpd_send_error(hreq, 902, "");
else
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2674,19 +2629,19 @@ dacp_reply_volumeup(struct httpd_request *hreq)
ret = speaker_get(&speaker_info, hreq, "volumeup");
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
ret = speaker_volume_step(&speaker_info, DACP_VOLUME_STEP);
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2700,19 +2655,19 @@ dacp_reply_volumedown(struct httpd_request *hreq)
ret = speaker_get(&speaker_info, hreq, "volumedown");
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
ret = speaker_volume_step(&speaker_info, -DACP_VOLUME_STEP);
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2726,7 +2681,7 @@ dacp_reply_mutetoggle(struct httpd_request *hreq)
ret = speaker_get(&speaker_info, hreq, "mutetoggle");
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
@@ -2734,12 +2689,12 @@ dacp_reply_mutetoggle(struct httpd_request *hreq)
ret = speaker_info.selected ? player_speaker_disable(speaker_info.id) : player_speaker_enable(speaker_info.id);
if (ret < 0)
{
- httpd_send_error(hreq->req, 500, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
return -1;
}
/* 204 No Content is the canonical reply */
- httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2800,7 +2755,7 @@ static struct httpd_uri_map dacp_handlers[] =
},
{
.regexp = "^/ctrl-int/[[:digit:]]+/playstatusupdate$",
- .handler = dacp_reply_playstatusupdate
+ .handler = dacp_reply_playstatusupdate,
},
{
.regexp = "^/ctrl-int/[[:digit:]]+/playqueue-contents$",
@@ -2851,57 +2806,27 @@ static struct httpd_uri_map dacp_handlers[] =
/* ------------------------------- DACP API --------------------------------- */
-void
-dacp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static void
+dacp_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
- struct evkeyvalq *headers;
-
- DPRINTF(E_DBG, L_DACP, "DACP request: '%s'\n", uri_parsed->uri);
-
- hreq = httpd_request_parse(req, uri_parsed, NULL, dacp_handlers);
- if (!hreq)
+ if (!hreq->handler)
{
- DPRINTF(E_LOG, L_DACP, "Unrecognized path '%s' in DACP request: '%s'\n", uri_parsed->path, uri_parsed->uri);
+ DPRINTF(E_LOG, L_DACP, "Unrecognized path in DACP request: '%s'\n", hreq->uri);
- httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return;
}
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "DAAP-Server", PACKAGE_NAME "/" VERSION);
+ httpd_header_add(hreq->out_headers, "DAAP-Server", PACKAGE_NAME "/" VERSION);
/* Content-Type for all DACP replies; can be overriden as needed */
- evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
-
- CHECK_NULL(L_DACP, hreq->reply = evbuffer_new());
+ httpd_header_add(hreq->out_headers, "Content-Type", "application/x-dmap-tagged");
hreq->handler(hreq);
-
- evbuffer_free(hreq->reply);
- free(hreq);
}
-int
-dacp_is_request(const char *path)
-{
- if (strncmp(path, "/ctrl-int/", strlen("/ctrl-int/")) == 0)
- return 1;
- if (strcmp(path, "/ctrl-int") == 0)
- return 1;
-
- return 0;
-}
-
-int
+static int
dacp_init(void)
{
- char buf[64];
- int i;
- int ret;
-
- current_rev = 2;
- update_requests = NULL;
-
dummy_mfi.id = DB_MEDIA_FILE_NON_PERSISTENT_ID;
dummy_mfi.title = CFG_NAME_UNKNOWN_TITLE;
dummy_mfi.artist = CFG_NAME_UNKNOWN_ARTIST;
@@ -2914,109 +2839,38 @@ dacp_init(void)
dummy_queue_item.album = CFG_NAME_UNKNOWN_ALBUM;
dummy_queue_item.genre = CFG_NAME_UNKNOWN_GENRE;
-#ifdef HAVE_EVENTFD
- update_efd = eventfd(0, EFD_CLOEXEC);
- if (update_efd < 0)
- {
- DPRINTF(E_LOG, L_DACP, "Could not create update eventfd: %s\n", strerror(errno));
-
- return -1;
- }
-#else
-# ifdef HAVE_PIPE2
- ret = pipe2(update_pipe, O_CLOEXEC);
-# else
- ret = pipe(update_pipe);
-# endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_DACP, "Could not create update pipe: %s\n", strerror(errno));
-
- return -1;
- }
-#endif /* HAVE_EVENTFD */
-
- for (i = 0; dacp_handlers[i].handler; i++)
- {
- ret = regcomp(&dacp_handlers[i].preg, dacp_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &dacp_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_DACP, "DACP init failed; regexp error: %s\n", buf);
- goto regexp_fail;
- }
- }
-
-#ifdef HAVE_EVENTFD
- updateev = event_new(evbase_httpd, update_efd, EV_READ, playstatusupdate_cb, NULL);
-#else
- updateev = event_new(evbase_httpd, update_pipe[0], EV_READ, playstatusupdate_cb, NULL);
-#endif
- if (!updateev)
- {
- DPRINTF(E_LOG, L_DACP, "Could not create update event\n");
-
- return -1;
- }
- event_add(updateev, NULL);
-
- seek_timer = evtimer_new(evbase_httpd, seek_timer_cb, NULL);
- if (!seek_timer)
- {
- DPRINTF(E_LOG, L_DACP, "Could not create seek_timer event\n");
-
- return -1;
- }
-
+ CHECK_ERR(L_DACP, mutex_init(&update_request_lck));
+ update_current_rev = 2;
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME | LISTENER_QUEUE);
return 0;
-
- regexp_fail:
-#ifdef HAVE_EVENTFD
- close(update_efd);
-#else
- close(update_pipe[0]);
- close(update_pipe[1]);
-#endif
- return -1;
}
-void
+static void
dacp_deinit(void)
{
struct dacp_update_request *ur;
- struct evhttp_connection *evcon;
- int i;
listener_remove(dacp_playstatus_update_handler);
- event_free(seek_timer);
-
- for (i = 0; dacp_handlers[i].handler; i++)
- regfree(&dacp_handlers[i].preg);
-
for (ur = update_requests; update_requests; ur = update_requests)
{
update_requests = ur->next;
- evcon = evhttp_request_get_connection(ur->req);
- if (evcon)
- {
- evhttp_connection_set_closecb(evcon, NULL, NULL);
- evhttp_connection_free(evcon);
- }
-
- free(ur);
+ httpd_send_error(ur->hreq, HTTP_SERVUNAVAIL, "Service Unavailable");
+ update_request_free(ur);
}
-
- event_free(updateev);
-
-#ifdef HAVE_EVENTFD
- close(update_efd);
-#else
- close(update_pipe[0]);
- close(update_pipe[1]);
-#endif
}
+
+struct httpd_module httpd_dacp =
+{
+ .name = "DACP",
+ .type = MODULE_DACP,
+ .logdomain = L_DACP,
+ .subpaths = { "/ctrl-int/", NULL },
+ .fullpaths = { "/ctrl-int", NULL },
+ .handlers = dacp_handlers,
+ .init = dacp_init,
+ .deinit = dacp_deinit,
+ .request = dacp_request,
+};
diff --git a/src/httpd_dacp.h b/src/httpd_dacp.h
deleted file mode 100644
index 307543dc..00000000
--- a/src/httpd_dacp.h
+++ /dev/null
@@ -1,19 +0,0 @@
-
-#ifndef __HTTPD_DACP_H__
-#define __HTTPD_DACP_H__
-
-#include "httpd.h"
-
-int
-dacp_init(void);
-
-void
-dacp_deinit(void);
-
-void
-dacp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-dacp_is_request(const char *path);
-
-#endif /* !__HTTPD_DACP_H__ */
diff --git a/src/httpd_internal.h b/src/httpd_internal.h
new file mode 100644
index 00000000..0157601e
--- /dev/null
+++ b/src/httpd_internal.h
@@ -0,0 +1,366 @@
+
+#ifndef __HTTPD_INTERNAL_H__
+#define __HTTPD_INTERNAL_H__
+
+#include
+#include
+#include
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+/* Response codes from event2/http.h */
+#define HTTP_CONTINUE 100 /**< client should proceed to send */
+#define HTTP_SWITCH_PROTOCOLS 101 /**< switching to another protocol */
+#define HTTP_PROCESSING 102 /**< processing the request, but no response is available yet */
+#define HTTP_EARLYHINTS 103 /**< return some response headers */
+#define HTTP_OK 200 /**< request completed ok */
+#define HTTP_CREATED 201 /**< new resource is created */
+#define HTTP_ACCEPTED 202 /**< accepted for processing */
+#define HTTP_NONAUTHORITATIVE 203 /**< returning a modified version of the origin's response */
+#define HTTP_NOCONTENT 204 /**< request does not have content */
+#define HTTP_MOVEPERM 301 /**< the uri moved permanently */
+#define HTTP_MOVETEMP 302 /**< the uri moved temporarily */
+#define HTTP_NOTMODIFIED 304 /**< page was not modified from last */
+#define HTTP_BADREQUEST 400 /**< invalid http request was made */
+#define HTTP_UNAUTHORIZED 401 /**< authentication is required */
+#define HTTP_PAYMENTREQUIRED 402 /**< user exceeded limit on requests */
+#define HTTP_FORBIDDEN 403 /**< user not having the necessary permissions */
+#define HTTP_NOTFOUND 404 /**< could not find content for uri */
+#define HTTP_BADMETHOD 405 /**< method not allowed for this uri */
+#define HTTP_ENTITYTOOLARGE 413 /**< request is larger than the server is able to process */
+#define HTTP_EXPECTATIONFAILED 417 /**< we can't handle this expectation */
+#define HTTP_INTERNAL 500 /**< internal error */
+#define HTTP_NOTIMPLEMENTED 501 /**< not implemented */
+#define HTTP_BADGATEWAY 502 /**< received an invalid response from the upstream */
+#define HTTP_SERVUNAVAIL 503 /**< the server is not available */
+
+
+struct httpd_request;
+
+// Declaring here instead of including event2/http.h makes it easier to support
+// other backends than evhttp in the future, e.g. libevhtp
+struct httpd_server;
+struct evhttp_connection;
+struct evhttp_request;
+struct evkeyvalq;
+struct httpd_uri_parsed;
+
+typedef struct httpd_server httpd_server;
+typedef struct evhttp_connection httpd_connection;
+typedef struct evhttp_request httpd_backend;
+typedef struct evkeyvalq httpd_headers;
+typedef struct evkeyvalq httpd_query;
+typedef struct httpd_uri_parsed httpd_uri_parsed;
+typedef struct httpd_backend_data httpd_backend_data;
+
+typedef char *httpd_uri_path_parts[31];
+typedef void (*httpd_request_cb)(struct httpd_request *hreq, void *arg);
+typedef void (*httpd_close_cb)(void *arg);
+typedef void (*httpd_connection_chunkcb)(httpd_connection *conn, void *arg);
+typedef void (*httpd_query_iteratecb)(const char *key, const char *val, void *arg);
+
+enum httpd_methods
+{
+ HTTPD_METHOD_GET = 1 << 0,
+ HTTPD_METHOD_POST = 1 << 1,
+ HTTPD_METHOD_HEAD = 1 << 2,
+ HTTPD_METHOD_PUT = 1 << 3,
+ HTTPD_METHOD_DELETE = 1 << 4,
+ HTTPD_METHOD_OPTIONS = 1 << 5,
+ HTTPD_METHOD_TRACE = 1 << 6,
+ HTTPD_METHOD_CONNECT = 1 << 7,
+ HTTPD_METHOD_PATCH = 1 << 8,
+};
+
+#define HTTPD_F_REPLY_LAST (1 << 15)
+enum httpd_reply_type
+{
+ HTTPD_REPLY_START = 1,
+ HTTPD_REPLY_CHUNK = 2,
+ HTTPD_REPLY_END = HTTPD_F_REPLY_LAST | 1,
+ HTTPD_REPLY_COMPLETE = HTTPD_F_REPLY_LAST | 2,
+};
+
+enum httpd_send_flags
+{
+ HTTPD_SEND_NO_GZIP = (1 << 0),
+};
+
+
+/*---------------------------------- MODULES ---------------------------------*/
+
+// Must be in sync with modules[] in httpd.c
+enum httpd_modules
+{
+ MODULE_DACP,
+ MODULE_DAAP,
+ MODULE_JSONAPI,
+ MODULE_ARTWORKAPI,
+ MODULE_STREAMING,
+ MODULE_OAUTH,
+ MODULE_RSP,
+};
+
+enum httpd_handler_flags
+{
+ // Most requests are pushed to a worker thread, but some handlers deal with
+ // requests that must be answered quickly. Can only be used for nonblocking
+ // handlers.
+ HTTPD_HANDLER_REALTIME = (1 << 0),
+};
+
+struct httpd_module
+{
+ const char *name;
+ enum httpd_modules type;
+ char initialized;
+ int logdomain;
+
+ // Null-terminated list of URL subpath that the module accepts e.g., /subpath/morepath/file.mp3
+ const char *subpaths[16];
+ // Null-terminated list of URL fullparhs that the module accepts e.g., /fullpath
+ const char *fullpaths[16];
+ // Pointer to the module's handler definitions
+ struct httpd_uri_map *handlers;
+
+ int (*init)(void);
+ void (*deinit)(void);
+ void (*request)(struct httpd_request *);
+};
+
+/*
+ * Maps a regex of the request path to a handler of the request
+ */
+struct httpd_uri_map
+{
+ enum httpd_methods method;
+ char *regexp;
+ int (*handler)(struct httpd_request *hreq);
+ void *preg;
+ int flags; // See enum httpd_handler_flags
+};
+
+
+/*------------------------------- HTTPD STRUCTS ------------------------------*/
+
+/*
+ * A collection of pointers to request data that the reply handlers may need.
+ * Also has the function pointer to the reply handler and a pointer to a reply
+ * evbuffer.
+ */
+struct httpd_request {
+ // Request method
+ enum httpd_methods method;
+ // Backend private request object
+ httpd_backend *backend;
+ // For storing data that the actual backend doesn't have readily available
+ httpd_backend_data *backend_data;
+ // User-agent (if available)
+ const char *user_agent;
+ // Source IP address (ipv4 or ipv6) and port of the request (if available)
+ const char *peer_address;
+ unsigned short peer_port;
+
+ // The original, request URI. The URI may have been complete:
+ // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
+ // or relative:
+ // [/path][?query][#fragment]
+ const char *uri;
+ // URI decoded path from the request URI
+ const char *path;
+ // If the request is http://x:3689/foo/bar?key1=val1, then part_parts[0] is
+ // "foo", [1] is "bar" and the rest is null. Each path_part is an allocated
+ // URI decoded string.
+ httpd_uri_path_parts path_parts;
+ // Struct with the query, used with httpd_query_ functions
+ httpd_query *query;
+ // Backend private parser URI object
+ httpd_uri_parsed *uri_parsed;
+
+ // Request headers
+ httpd_headers *in_headers;
+ // Request body
+ struct evbuffer *in_body;
+ // Response headers
+ httpd_headers *out_headers;
+ // Response body
+ struct evbuffer *out_body;
+
+ // Our httpd module that will process this request
+ struct httpd_module *module;
+ // A pointer to the handler that will process the request
+ int (*handler)(struct httpd_request *hreq);
+ // Is the processing defered to a worker thread
+ bool is_async;
+ // Handler thread's evbase in case the handler needs to scehdule an event
+ struct event_base *evbase;
+ // A pointer to extra data that the module handling the request might need
+ void *extra_data;
+};
+
+
+/*------------------------------ HTTPD FUNCTIONS -----------------------------*/
+
+void
+httpd_stream_file(struct httpd_request *hreq, int id);
+
+void
+httpd_request_handler_set(struct httpd_request *hreq);
+
+bool
+httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime);
+
+bool
+httpd_request_etag_matches(struct httpd_request *hreq, const char *etag);
+
+void
+httpd_response_not_cachable(struct httpd_request *hreq);
+
+/*
+ * This wrapper around evhttp_send_reply should be used whenever a request may
+ * come from a browser. It will automatically gzip if feasible, but the caller
+ * may direct it not to. It will set CORS headers as appropriate. Should be
+ * thread safe.
+ *
+ * @in req The http request struct
+ * @in code HTTP code, e.g. 200
+ * @in reason A brief explanation of the error - if NULL the standard meaning
+ of the error code will be used
+ * @in flags See flags above
+ */
+void
+httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, enum httpd_send_flags flags);
+
+void
+httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason);
+
+void
+httpd_send_reply_chunk(struct httpd_request *hreq, httpd_connection_chunkcb cb, void *arg);
+
+void
+httpd_send_reply_end(struct httpd_request *hreq);
+
+/*
+ * This is a substitute for evhttp_send_error that should be used whenever an
+ * error may be returned to a browser. It will set CORS headers as appropriate,
+ * which is not possible with evhttp_send_error, because it clears the headers.
+ * Should be thread safe.
+ *
+ * @in req The http request struct
+ * @in error HTTP code, e.g. 200
+ * @in reason A brief explanation of the error - if NULL the standard meaning
+ of the error code will be used
+ */
+void
+httpd_send_error(struct httpd_request *hreq, int error, const char *reason);
+
+
+void
+httpd_redirect_to(struct httpd_request *hreq, const char *path);
+
+bool
+httpd_admin_check_auth(struct httpd_request *hreq);
+
+int
+httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm);
+
+
+/*-------------------------- WRAPPERS FOR EVHTTP -----------------------------*/
+
+const char *
+httpd_query_value_find(httpd_query *query, const char *key);
+
+void
+httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg);
+
+void
+httpd_query_clear(httpd_query *query);
+
+const char *
+httpd_header_find(httpd_headers *headers, const char *key);
+
+void
+httpd_header_remove(httpd_headers *headers, const char *key);
+
+void
+httpd_header_add(httpd_headers *headers, const char *key, const char *val);
+
+void
+httpd_headers_clear(httpd_headers *headers);
+
+void
+httpd_request_close_cb_set(struct httpd_request *hreq, httpd_close_cb cb, void *arg);
+
+void
+httpd_request_free(struct httpd_request *hreq);
+
+struct httpd_request *
+httpd_request_new(httpd_backend *backend, httpd_server *server, const char *uri, const char *user_agent);
+
+void
+httpd_server_free(httpd_server *server);
+
+httpd_server *
+httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_cb cb, void *arg);
+
+void
+httpd_server_allow_origin_set(httpd_server *server, bool allow);
+
+
+/*----------------- Only called by httpd.c to send raw replies ---------------*/
+
+void
+httpd_send(struct httpd_request *hreq, enum httpd_reply_type type, int code, const char *reason,
+ httpd_connection_chunkcb cb, void *cbarg);
+
+
+/*---------- Only called by httpd.c to populate struct httpd_request ---------*/
+
+httpd_backend_data *
+httpd_backend_data_create(httpd_backend *backend, httpd_server *server);
+
+void
+httpd_backend_data_free(httpd_backend_data *backend_data);
+
+struct event_base *
+httpd_backend_evbase_get(httpd_backend *backend);
+
+const char *
+httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data);
+
+httpd_headers *
+httpd_backend_input_headers_get(httpd_backend *backend);
+
+httpd_headers *
+httpd_backend_output_headers_get(httpd_backend *backend);
+
+struct evbuffer *
+httpd_backend_input_buffer_get(httpd_backend *backend);
+
+int
+httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data);
+
+int
+httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend);
+
+httpd_uri_parsed *
+httpd_uri_parsed_create(httpd_backend *backend);
+
+httpd_uri_parsed *
+httpd_uri_parsed_create_fromuri(const char *uri);
+
+void
+httpd_uri_parsed_free(httpd_uri_parsed *uri_parsed);
+
+httpd_query *
+httpd_uri_query_get(httpd_uri_parsed *parsed);
+
+const char *
+httpd_uri_path_get(httpd_uri_parsed *parsed);
+
+void
+httpd_uri_path_parts_get(httpd_uri_path_parts *part_parts, httpd_uri_parsed *parsed);
+
+#endif /* !__HTTPD_INTERNAL_H__ */
diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c
index 56cdb771..cd7e319b 100644
--- a/src/httpd_jsonapi.c
+++ b/src/httpd_jsonapi.c
@@ -34,14 +34,13 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include "httpd_jsonapi.h"
+#include "httpd_internal.h"
#include "conffile.h"
#include "db.h"
#ifdef LASTFM
@@ -68,13 +67,13 @@ static char *default_playlist_directory;
/* -------------------------------- HELPERS --------------------------------- */
static bool
-is_modified(struct evhttp_request *req, const char *key)
+is_modified(struct httpd_request *hreq, const char *key)
{
int64_t db_update = 0;
db_admin_getint64(&db_update, key);
- return (!db_update || !httpd_request_not_modified_since(req, (time_t)db_update));
+ return (!db_update || !httpd_request_not_modified_since(hreq, (time_t)db_update));
}
static inline void
@@ -764,7 +763,7 @@ query_params_limit_set(struct query_params *query_params, struct httpd_request *
query_params->limit = -1;
query_params->offset = 0;
- param = evhttp_find_header(hreq->query, "limit");
+ param = httpd_query_value_find(hreq->query, "limit");
if (param)
{
query_params->idx_type = I_SUB;
@@ -775,7 +774,7 @@ query_params_limit_set(struct query_params *query_params, struct httpd_request *
return -1;
}
- param = evhttp_find_header(hreq->query, "offset");
+ param = httpd_query_value_find(hreq->query, "offset");
if (param && safe_atoi32(param, &query_params->offset) < 0)
{
DPRINTF(E_LOG, L_WEB, "Invalid value for query parameter 'offset' (%s)\n", param);
@@ -867,7 +866,7 @@ jsonapi_reply_config(struct httpd_request *hreq)
json_object_object_add(jreply, "allow_modifying_stored_playlists", json_object_new_boolean(allow_modifying_stored_playlists));
safe_json_add_string(jreply, "default_playlist_directory", default_playlist_directory);
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -974,7 +973,7 @@ jsonapi_reply_settings_get(struct httpd_request *hreq)
json_object_object_add(jreply, "categories", json_categories);
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -989,7 +988,7 @@ jsonapi_reply_settings_category_get(struct httpd_request *hreq)
json_object *jreply;
- categoryname = hreq->uri_parsed->path_parts[2];
+ categoryname = hreq->path_parts[2];
category = settings_category_get(categoryname);
if (!category)
@@ -1006,7 +1005,7 @@ jsonapi_reply_settings_category_get(struct httpd_request *hreq)
return HTTP_INTERNAL;
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1023,8 +1022,8 @@ jsonapi_reply_settings_option_get(struct httpd_request *hreq)
json_object *jreply;
- categoryname = hreq->uri_parsed->path_parts[2];
- optionname = hreq->uri_parsed->path_parts[3];
+ categoryname = hreq->path_parts[2];
+ optionname = hreq->path_parts[3];
category = settings_category_get(categoryname);
if (!category)
@@ -1048,7 +1047,7 @@ jsonapi_reply_settings_option_get(struct httpd_request *hreq)
return HTTP_INTERNAL;
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1062,7 +1061,6 @@ jsonapi_reply_settings_option_put(struct httpd_request *hreq)
const char *optionname;
struct settings_category *category;
struct settings_option *option;
- struct evbuffer *in_evbuf;
json_object* request;
int intval;
bool boolval;
@@ -1070,8 +1068,8 @@ jsonapi_reply_settings_option_put(struct httpd_request *hreq)
int ret;
- categoryname = hreq->uri_parsed->path_parts[2];
- optionname = hreq->uri_parsed->path_parts[3];
+ categoryname = hreq->path_parts[2];
+ optionname = hreq->path_parts[3];
category = settings_category_get(categoryname);
if (!category)
@@ -1088,8 +1086,7 @@ jsonapi_reply_settings_option_put(struct httpd_request *hreq)
return HTTP_NOTFOUND;
}
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Missing request body for setting option '%s' (type %d)\n", optionname, option->type);
@@ -1137,8 +1134,8 @@ jsonapi_reply_settings_option_delete(struct httpd_request *hreq)
int ret;
- categoryname = hreq->uri_parsed->path_parts[2];
- optionname = hreq->uri_parsed->path_parts[3];
+ categoryname = hreq->path_parts[2];
+ optionname = hreq->path_parts[3];
category = settings_category_get(categoryname);
if (!category)
@@ -1238,7 +1235,7 @@ jsonapi_reply_library(struct httpd_request *hreq)
}
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
return HTTP_OK;
@@ -1252,7 +1249,7 @@ jsonapi_reply_update(struct httpd_request *hreq)
{
const char *param;
- param = evhttp_find_header(hreq->query, "scan_kind");
+ param = httpd_query_value_find(hreq->query, "scan_kind");
library_rescan(db_scan_kind_enum(param));
return HTTP_NOCONTENT;
@@ -1263,7 +1260,7 @@ jsonapi_reply_meta_rescan(struct httpd_request *hreq)
{
const char *param;
- param = evhttp_find_header(hreq->query, "scan_kind");
+ param = httpd_query_value_find(hreq->query, "scan_kind");
library_metarescan(db_scan_kind_enum(param));
return HTTP_NOCONTENT;
@@ -1331,7 +1328,7 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
json_object_object_add(jreply, "enabled", json_object_new_boolean(false));
#endif
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1365,7 +1362,7 @@ jsonapi_reply_lastfm(struct httpd_request *hreq)
json_object_object_add(jreply, "enabled", json_object_new_boolean(enabled));
json_object_object_add(jreply, "scrobbling_enabled", json_object_new_boolean(scrobbling_enabled));
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1379,7 +1376,6 @@ static int
jsonapi_reply_lastfm_login(struct httpd_request *hreq)
{
#ifdef LASTFM
- struct evbuffer *in_evbuf;
json_object *request;
const char *user;
const char *password;
@@ -1390,8 +1386,7 @@ jsonapi_reply_lastfm_login(struct httpd_request *hreq)
DPRINTF(E_DBG, L_WEB, "Received LastFM login request\n");
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
@@ -1434,7 +1429,7 @@ jsonapi_reply_lastfm_login(struct httpd_request *hreq)
json_object_object_add(jreply, "errors", errors);
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1466,13 +1461,11 @@ jsonapi_reply_lastfm_logout(struct httpd_request *hreq)
static int
jsonapi_reply_pairing_pair(struct httpd_request *hreq)
{
- struct evbuffer *evbuf;
json_object* request;
const char* pin;
int ret;
- evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
@@ -1532,7 +1525,7 @@ jsonapi_reply_pairing_get(struct httpd_request *hreq)
json_object_object_add(jreply, "active", json_object_new_boolean(false));
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
free(remote_name);
@@ -1591,10 +1584,10 @@ jsonapi_reply_outputs_get_byid(struct httpd_request *hreq)
json_object *jreply;
int ret;
- ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
+ ret = safe_atou64(hreq->path_parts[2], &output_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -1603,13 +1596,13 @@ jsonapi_reply_outputs_get_byid(struct httpd_request *hreq)
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No output found for '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No output found for '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
jreply = speaker_to_json(&speaker_info);
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1623,23 +1616,21 @@ static int
jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
{
uint64_t output_id;
- struct evbuffer *in_evbuf;
json_object* request;
bool selected;
int volume;
const char *pin;
int ret;
- ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
+ ret = safe_atou64(hreq->path_parts[2], &output_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
@@ -1689,10 +1680,10 @@ jsonapi_reply_outputs_toggle_byid(struct httpd_request *hreq)
struct player_speaker_info spk;
int ret;
- ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
+ ret = safe_atou64(hreq->path_parts[2], &output_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -1700,7 +1691,7 @@ jsonapi_reply_outputs_toggle_byid(struct httpd_request *hreq)
ret = player_speaker_get_byid(&spk, output_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No output found for the given output id, toggle failed for '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No output found for the given output id, toggle failed for '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -1731,7 +1722,7 @@ jsonapi_reply_outputs(struct httpd_request *hreq)
jreply = json_object_new_object();
json_object_object_add(jreply, "outputs", outputs);
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@@ -1741,12 +1732,10 @@ jsonapi_reply_outputs(struct httpd_request *hreq)
static int
jsonapi_reply_verification(struct httpd_request *hreq)
{
- struct evbuffer *in_evbuf;
json_object* request;
const char* message;
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
@@ -1769,15 +1758,13 @@ jsonapi_reply_verification(struct httpd_request *hreq)
static int
jsonapi_reply_outputs_set(struct httpd_request *hreq)
{
- struct evbuffer *in_evbuf;
json_object *request;
json_object *outputs;
json_object *output_id;
int nspk, i, ret;
uint64_t *ids;
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
@@ -1902,11 +1889,11 @@ jsonapi_reply_player_play(struct httpd_request *hreq)
const char *param;
int ret;
- if ((param = evhttp_find_header(hreq->query, "item_id")))
+ if ((param = httpd_query_value_find(hreq->query, "item_id")))
{
return play_item_with_id(param);
}
- else if ((param = evhttp_find_header(hreq->query, "position")))
+ else if ((param = httpd_query_value_find(hreq->query, "position")))
{
return play_item_at_position(param);
}
@@ -2033,8 +2020,8 @@ jsonapi_reply_player_seek(struct httpd_request *hreq)
int seek_ms;
int ret;
- param_pos = evhttp_find_header(hreq->query, "position_ms");
- param_seek = evhttp_find_header(hreq->query, "seek_ms");
+ param_pos = httpd_query_value_find(hreq->query, "position_ms");
+ param_seek = httpd_query_value_find(hreq->query, "seek_ms");
if (!param_pos && !param_seek)
return HTTP_BADREQUEST;
@@ -2144,7 +2131,7 @@ jsonapi_reply_player(struct httpd_request *hreq)
}
}
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply)));
jparse_free(reply);
@@ -2472,7 +2459,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
int ret = 0;
- param_pos = evhttp_find_header(hreq->query, "position");
+ param_pos = httpd_query_value_find(hreq->query, "position");
if (param_pos)
{
if (safe_atoi32(param_pos, &pos) < 0)
@@ -2487,8 +2474,8 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
else
pos = -1;
- param_uris = evhttp_find_header(hreq->query, "uris");
- param_expression = evhttp_find_header(hreq->query, "expression");
+ param_uris = httpd_query_value_find(hreq->query, "uris");
+ param_expression = httpd_query_value_find(hreq->query, "expression");
if (!param_uris && !param_expression)
{
@@ -2498,7 +2485,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
}
// if query parameter "clear" is "true", stop playback and clear the queue before adding new queue items
- param = evhttp_find_header(hreq->query, "clear");
+ param = httpd_query_value_find(hreq->query, "clear");
if (param && strcmp(param, "true") == 0)
{
player_playback_stop();
@@ -2506,7 +2493,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
}
// if query parameter "shuffle" is present, update the shuffle state before adding new queue items
- param = evhttp_find_header(hreq->query, "shuffle");
+ param = httpd_query_value_find(hreq->query, "shuffle");
if (param)
{
shuffle = (strcmp(param, "true") == 0);
@@ -2520,7 +2507,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
else
{
// This overrides the value specified in query
- param = evhttp_find_header(hreq->query, "limit");
+ param = httpd_query_value_find(hreq->query, "limit");
if (param && safe_atoi32(param, &limit) == 0)
ret = queue_tracks_add_byexpression(param_expression, pos, limit, &total_count);
else
@@ -2532,7 +2519,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
reply = json_object_new_object();
json_object_object_add(reply, "count", json_object_new_int(total_count));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
jparse_free(reply);
}
@@ -2540,10 +2527,10 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
return HTTP_INTERNAL;
// If query parameter "playback" is "start", start playback after successfully adding new items
- param = evhttp_find_header(hreq->query, "playback");
+ param = httpd_query_value_find(hreq->query, "playback");
if (param && strcmp(param, "start") == 0)
{
- if ((param = evhttp_find_header(hreq->query, "playback_from_position")))
+ if ((param = httpd_query_value_find(hreq->query, "playback_from_position")))
ret = (play_item_at_position(param) == HTTP_NOCONTENT) ? 0 : -1;
else
ret = player_playback_start();
@@ -2597,12 +2584,12 @@ jsonapi_reply_queue_tracks_update(struct httpd_request *hreq)
player_get_status(&status);
- if (strcmp(hreq->uri_parsed->path_parts[3], "now_playing") != 0)
+ if (strcmp(hreq->path_parts[3], "now_playing") != 0)
{
- ret = safe_atou32(hreq->uri_parsed->path_parts[3], &item_id);
+ ret = safe_atou32(hreq->path_parts[3], &item_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid item id given: '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid item id given: '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
}
@@ -2612,27 +2599,27 @@ jsonapi_reply_queue_tracks_update(struct httpd_request *hreq)
queue_item = db_queue_fetch_byitemid(item_id);
if (!queue_item)
{
- DPRINTF(E_LOG, L_WEB, "No valid item id given, or now_playing given but not playing: '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid item id given, or now_playing given but not playing: '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
ret = HTTP_OK;
is_changed = false;
- if ((param = evhttp_find_header(hreq->query, "new_position")))
+ if ((param = httpd_query_value_find(hreq->query, "new_position")))
ret = update_pos(item_id, param, status.shuffle);
- if ((param = evhttp_find_header(hreq->query, "title")))
+ if ((param = httpd_query_value_find(hreq->query, "title")))
update_str(&is_changed, &queue_item->title, param);
- if ((param = evhttp_find_header(hreq->query, "album")))
+ if ((param = httpd_query_value_find(hreq->query, "album")))
update_str(&is_changed, &queue_item->album, param);
- if ((param = evhttp_find_header(hreq->query, "artist")))
+ if ((param = httpd_query_value_find(hreq->query, "artist")))
update_str(&is_changed, &queue_item->artist, param);
- if ((param = evhttp_find_header(hreq->query, "album_artist")))
+ if ((param = httpd_query_value_find(hreq->query, "album_artist")))
update_str(&is_changed, &queue_item->album_artist, param);
- if ((param = evhttp_find_header(hreq->query, "composer")))
+ if ((param = httpd_query_value_find(hreq->query, "composer")))
update_str(&is_changed, &queue_item->composer, param);
- if ((param = evhttp_find_header(hreq->query, "genre")))
+ if ((param = httpd_query_value_find(hreq->query, "genre")))
update_str(&is_changed, &queue_item->genre, param);
- if ((param = evhttp_find_header(hreq->query, "artwork_url")))
+ if ((param = httpd_query_value_find(hreq->query, "artwork_url")))
update_str(&is_changed, &queue_item->artwork_url, param);
if (ret != HTTP_OK)
@@ -2650,10 +2637,10 @@ jsonapi_reply_queue_tracks_delete(struct httpd_request *hreq)
uint32_t item_id;
int ret;
- ret = safe_atou32(hreq->uri_parsed->path_parts[3], &item_id);
+ ret = safe_atou32(hreq->path_parts[3], &item_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid item id given '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid item id given '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -2697,7 +2684,7 @@ jsonapi_reply_queue(struct httpd_request *hreq)
db_queue_get_count(&count);
snprintf(etag, sizeof(etag), "%d", version);
- if (httpd_request_etag_matches(hreq->req, etag))
+ if (httpd_request_etag_matches(hreq, etag))
return HTTP_NOTMODIFIED;
memset(&query_params, 0, sizeof(struct query_params));
@@ -2713,7 +2700,7 @@ jsonapi_reply_queue(struct httpd_request *hreq)
if (status.shuffle)
query_params.sort = S_SHUFFLE_POS;
- param = evhttp_find_header(hreq->query, "id");
+ param = httpd_query_value_find(hreq->query, "id");
if (param && strcmp(param, "now_playing") == 0)
{
query_params.filter = db_mprintf("id = %d", status.item_id);
@@ -2724,10 +2711,10 @@ jsonapi_reply_queue(struct httpd_request *hreq)
}
else
{
- param = evhttp_find_header(hreq->query, "start");
+ param = httpd_query_value_find(hreq->query, "start");
if (param && safe_atoi32(param, &start_pos) == 0)
{
- param = evhttp_find_header(hreq->query, "end");
+ param = httpd_query_value_find(hreq->query, "end");
if (!param || safe_atoi32(param, &end_pos) != 0)
{
end_pos = start_pos + 1;
@@ -2753,7 +2740,7 @@ jsonapi_reply_queue(struct httpd_request *hreq)
json_object_array_add(items, item);
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "outputs: Couldn't add outputs to response buffer.\n");
@@ -2774,7 +2761,7 @@ jsonapi_reply_player_repeat(struct httpd_request *hreq)
{
const char *param;
- param = evhttp_find_header(hreq->query, "state");
+ param = httpd_query_value_find(hreq->query, "state");
if (!param)
return HTTP_BADREQUEST;
@@ -2800,7 +2787,7 @@ jsonapi_reply_player_shuffle(struct httpd_request *hreq)
const char *param;
bool shuffle;
- param = evhttp_find_header(hreq->query, "state");
+ param = httpd_query_value_find(hreq->query, "state");
if (!param)
return HTTP_BADREQUEST;
@@ -2816,7 +2803,7 @@ jsonapi_reply_player_consume(struct httpd_request *hreq)
const char *param;
bool consume;
- param = evhttp_find_header(hreq->query, "state");
+ param = httpd_query_value_find(hreq->query, "state");
if (!param)
return HTTP_BADREQUEST;
@@ -2895,7 +2882,7 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
step = 0;
// Parse and validate parameters
- param_volume = evhttp_find_header(hreq->query, "volume");
+ param_volume = httpd_query_value_find(hreq->query, "volume");
if (param_volume)
{
ret = safe_atoi32(param_volume, &volume);
@@ -2903,7 +2890,7 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
return HTTP_BADREQUEST;
}
- param_step = evhttp_find_header(hreq->query, "step");
+ param_step = httpd_query_value_find(hreq->query, "step");
if (param_step)
{
ret = safe_atoi32(param_step, &step);
@@ -2918,7 +2905,7 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
return HTTP_BADREQUEST;
}
- param = evhttp_find_header(hreq->query, "output_id");
+ param = httpd_query_value_find(hreq->query, "output_id");
if (param)
{
// Update volume for individual output
@@ -2953,11 +2940,11 @@ jsonapi_reply_library_artists(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
media_kind = 0;
- param = evhttp_find_header(hreq->query, "media_kind");
+ param = httpd_query_value_find(hreq->query, "media_kind");
if (param)
{
media_kind = db_media_kind_enum(param);
@@ -2992,7 +2979,7 @@ jsonapi_reply_library_artists(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add artists to response buffer.\n");
@@ -3014,10 +3001,10 @@ jsonapi_reply_library_artist(struct httpd_request *hreq)
int ret = 0;
bool notfound = false;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- artist_id = hreq->uri_parsed->path_parts[3];
+ artist_id = hreq->path_parts[3];
reply = fetch_artist(¬found, artist_id);
if (!reply)
@@ -3026,7 +3013,7 @@ jsonapi_reply_library_artist(struct httpd_request *hreq)
goto error;
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add artists to response buffer.\n");
@@ -3049,10 +3036,10 @@ jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- artist_id = hreq->uri_parsed->path_parts[3];
+ artist_id = hreq->path_parts[3];
reply = json_object_new_object();
items = json_object_new_array();
@@ -3078,7 +3065,7 @@ jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add albums to response buffer.\n");
@@ -3102,11 +3089,11 @@ jsonapi_reply_library_albums(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
media_kind = 0;
- param = evhttp_find_header(hreq->query, "media_kind");
+ param = httpd_query_value_find(hreq->query, "media_kind");
if (param)
{
media_kind = db_media_kind_enum(param);
@@ -3141,7 +3128,7 @@ jsonapi_reply_library_albums(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add albums to response buffer.\n");
@@ -3163,10 +3150,10 @@ jsonapi_reply_library_album(struct httpd_request *hreq)
int ret = 0;
bool notfound = false;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- album_id = hreq->uri_parsed->path_parts[3];
+ album_id = hreq->path_parts[3];
reply = fetch_album(¬found, album_id);
if (!reply)
@@ -3175,7 +3162,7 @@ jsonapi_reply_library_album(struct httpd_request *hreq)
goto error;
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add artists to response buffer.\n");
@@ -3198,10 +3185,10 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
+ if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
return HTTP_NOTMODIFIED;
- album_id = hreq->uri_parsed->path_parts[3];
+ album_id = hreq->path_parts[3];
reply = json_object_new_object();
items = json_object_new_array();
@@ -3227,7 +3214,7 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add tracks to response buffer.\n");
@@ -3247,11 +3234,11 @@ jsonapi_reply_library_album_tracks_put_byid(struct httpd_request *hreq)
int64_t album_id;;
int ret;
- ret = safe_atoi64(hreq->uri_parsed->path_parts[3], &album_id);
+ ret = safe_atoi64(hreq->path_parts[3], &album_id);
if (ret < 0)
return HTTP_INTERNAL;
- param = evhttp_find_header(hreq->query, "play_count");
+ param = httpd_query_value_find(hreq->query, "play_count");
if (!param)
return HTTP_BADREQUEST;
@@ -3282,10 +3269,10 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq)
int ret = 0;
bool notfound = false;
- if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
+ if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
return HTTP_NOTMODIFIED;
- track_id = hreq->uri_parsed->path_parts[3];
+ track_id = hreq->path_parts[3];
memset(&query_params, 0, sizeof(struct query_params));
@@ -3309,7 +3296,7 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq)
reply = track_to_json(&dbmfi);
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add track to response buffer.\n");
@@ -3327,7 +3314,6 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq)
static int
jsonapi_reply_library_tracks_put(struct httpd_request *hreq)
{
- struct evbuffer *in_evbuf;
json_object *request = NULL;
json_object *tracks;
json_object *track = NULL;
@@ -3337,8 +3323,7 @@ jsonapi_reply_library_tracks_put(struct httpd_request *hreq)
int32_t track_id;
int i;
- in_evbuf = evhttp_request_get_input_buffer(hreq->req);
- request = jparse_obj_from_evbuffer(in_evbuf);
+ request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to read json tracks request\n");
@@ -3413,11 +3398,11 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq)
uint32_t val;
int ret;
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &track_id);
+ ret = safe_atoi32(hreq->path_parts[3], &track_id);
if (ret < 0)
return HTTP_INTERNAL;
- param = evhttp_find_header(hreq->query, "play_count");
+ param = httpd_query_value_find(hreq->query, "play_count");
if (param)
{
if (strcmp(param, "increment") == 0)
@@ -3435,7 +3420,7 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq)
}
}
- param = evhttp_find_header(hreq->query, "rating");
+ param = httpd_query_value_find(hreq->query, "rating");
if (param)
{
ret = safe_atou32(param, &val);
@@ -3451,7 +3436,7 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq)
}
// Retreive marked tracks via "/api/search?type=tracks&expression=usermark+=+1"
- param = evhttp_find_header(hreq->query, "usermark");
+ param = httpd_query_value_find(hreq->query, "usermark");
if (param)
{
ret = safe_atou32(param, &val);
@@ -3481,10 +3466,10 @@ jsonapi_reply_library_track_playlists(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
+ if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
return HTTP_NOTMODIFIED;
- track_id = hreq->uri_parsed->path_parts[3];
+ track_id = hreq->path_parts[3];
if (safe_atoi32(track_id, &id) < 0)
{
DPRINTF(E_LOG, L_WEB, "Error converting track id '%s' to int.\n", track_id);
@@ -3519,7 +3504,7 @@ jsonapi_reply_library_track_playlists(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "track playlists: Couldn't add playlists to response buffer.\n");
@@ -3543,7 +3528,7 @@ jsonapi_reply_library_playlists(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
reply = json_object_new_object();
@@ -3570,7 +3555,7 @@ jsonapi_reply_library_playlists(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add playlists to response buffer.\n");
@@ -3591,10 +3576,10 @@ jsonapi_reply_library_playlist_get(struct httpd_request *hreq)
int ret = 0;
bool notfound = false;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- ret = safe_atou32(hreq->uri_parsed->path_parts[3], &playlist_id);
+ ret = safe_atou32(hreq->path_parts[3], &playlist_id);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Could not parse playlist id to integer\n");
@@ -3621,7 +3606,7 @@ jsonapi_reply_library_playlist_get(struct httpd_request *hreq)
goto error;
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add playlist to response buffer.\n");
@@ -3665,14 +3650,14 @@ jsonapi_reply_library_playlist_put(struct httpd_request *hreq)
const char *param;
int ret;
- ret = safe_atou32(hreq->uri_parsed->path_parts[3], &playlist_id);
+ ret = safe_atou32(hreq->path_parts[3], &playlist_id);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Could not parse playlist id to integer\n");
return HTTP_BADREQUEST;
}
- if ((param = evhttp_find_header(hreq->query, "query_limit")))
+ if ((param = httpd_query_value_find(hreq->query, "query_limit")))
ret = playlist_attrib_query_limit_set(playlist_id, param);
else
ret = -1;
@@ -3694,12 +3679,12 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
int ret = 0;
// Due to smart playlists possibly changing their tracks between rescans, disable caching in clients
- httpd_response_not_cachable(hreq->req);
+ httpd_response_not_cachable(hreq);
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
+ ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -3725,7 +3710,7 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "playlist tracks: Couldn't add tracks to response buffer.\n");
@@ -3745,10 +3730,10 @@ jsonapi_reply_library_playlist_delete(struct httpd_request *hreq)
uint32_t pl_id;
int ret;
- ret = safe_atou32(hreq->uri_parsed->path_parts[3], &pl_id);
+ ret = safe_atou32(hreq->path_parts[3], &pl_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -3768,14 +3753,14 @@ jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq)
int total;
int ret = 0;
- if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
+ if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
return HTTP_NOTMODIFIED;
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
+ ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
+ DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
return HTTP_BADREQUEST;
}
@@ -3803,7 +3788,7 @@ jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "playlist tracks: Couldn't add tracks to response buffer.\n");
@@ -3824,11 +3809,11 @@ jsonapi_reply_library_playlist_tracks_put_byid(struct httpd_request *hreq)
int playlist_id;
int ret;
- ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
+ ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
if (ret < 0)
return HTTP_INTERNAL;
- param = evhttp_find_header(hreq->query, "play_count");
+ param = httpd_query_value_find(hreq->query, "play_count");
if (!param)
return HTTP_BADREQUEST;
@@ -3857,7 +3842,7 @@ jsonapi_reply_queue_save(struct httpd_request *hreq)
char *playlist_name = NULL;
int ret = 0;
- if ((param = evhttp_find_header(hreq->query, "name")) == NULL)
+ if ((param = httpd_query_value_find(hreq->query, "name")) == NULL)
{
DPRINTF(E_LOG, L_WEB, "Invalid argument, missing 'name'\n");
return HTTP_BADREQUEST;
@@ -3907,14 +3892,14 @@ jsonapi_reply_library_browse(struct httpd_request *hreq)
int total;
int ret;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- browse_type = hreq->uri_parsed->path_parts[2];
+ browse_type = hreq->path_parts[2];
DPRINTF(E_DBG, L_WEB, "Browse query with type '%s'\n", browse_type);
media_kind = 0;
- param = evhttp_find_header(hreq->query, "media_kind");
+ param = httpd_query_value_find(hreq->query, "media_kind");
if (param)
{
media_kind = db_media_kind_enum(param);
@@ -3964,7 +3949,7 @@ jsonapi_reply_library_browse(struct httpd_request *hreq)
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse items to response buffer.\n");
@@ -3988,11 +3973,11 @@ jsonapi_reply_library_browseitem(struct httpd_request *hreq)
json_object *reply;
int ret;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
- browse_type = hreq->uri_parsed->path_parts[2];
- item_name = hreq->uri_parsed->path_parts[3];
+ browse_type = hreq->path_parts[2];
+ item_name = hreq->path_parts[3];
DPRINTF(E_DBG, L_WEB, "Browse item query with type '%s'\n", browse_type);
reply = json_object_new_object();
@@ -4037,7 +4022,7 @@ jsonapi_reply_library_browseitem(struct httpd_request *hreq)
goto error;
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse item to response buffer.\n");
@@ -4063,13 +4048,13 @@ jsonapi_reply_library_count(struct httpd_request *hreq)
json_object *jreply;
int ret;
- if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
+ if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_COUNT_ITEMS;
- param_expression = evhttp_find_header(hreq->query, "expression");
+ param_expression = httpd_query_value_find(hreq->query, "expression");
if (param_expression)
{
memset(&smartpl_expression, 0, sizeof(struct smartpl));
@@ -4102,7 +4087,7 @@ jsonapi_reply_library_count(struct httpd_request *hreq)
free(qp.filter);
- CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
+ CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
return HTTP_OK;
@@ -4123,7 +4108,7 @@ jsonapi_reply_library_files(struct httpd_request *hreq)
int total;
int ret;
- param = evhttp_find_header(hreq->query, "directory");
+ param = httpd_query_value_find(hreq->query, "directory");
directory_id = DIR_FILE;
if (param)
@@ -4196,7 +4181,7 @@ jsonapi_reply_library_files(struct httpd_request *hreq)
json_object_object_add(playlists, "limit", json_object_new_int(query_params.limit));
// Build JSON response
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add directories to response buffer.\n");
@@ -4215,7 +4200,7 @@ jsonapi_reply_library_add(struct httpd_request *hreq)
const char *url;
int ret;
- url = evhttp_find_header(hreq->query, "url");
+ url = httpd_query_value_find(hreq->query, "url");
if (!url)
{
DPRINTF(E_LOG, L_WEB, "Missing URL parameter for library add\n");
@@ -4528,9 +4513,9 @@ jsonapi_reply_search(struct httpd_request *hreq)
reply = NULL;
- param_type = evhttp_find_header(hreq->query, "type");
- param_query = evhttp_find_header(hreq->query, "query");
- param_expression = evhttp_find_header(hreq->query, "expression");
+ param_type = httpd_query_value_find(hreq->query, "type");
+ param_query = httpd_query_value_find(hreq->query, "query");
+ param_expression = httpd_query_value_find(hreq->query, "expression");
if (!param_type || (!param_query && !param_expression))
{
@@ -4540,7 +4525,7 @@ jsonapi_reply_search(struct httpd_request *hreq)
}
media_kind = 0;
- param_media_kind = evhttp_find_header(hreq->query, "media_kind");
+ param_media_kind = httpd_query_value_find(hreq->query, "media_kind");
if (param_media_kind)
{
media_kind = db_media_kind_enum(param_media_kind);
@@ -4601,7 +4586,7 @@ jsonapi_reply_search(struct httpd_request *hreq)
goto error;
}
- ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
+ ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "playlist tracks: Couldn't add tracks to response buffer.\n");
@@ -4635,80 +4620,80 @@ jsonapi_reply_library_backup(struct httpd_request *hreq)
static struct httpd_uri_map adm_handlers[] =
{
- { EVHTTP_REQ_GET, "^/api/config$", jsonapi_reply_config },
- { EVHTTP_REQ_GET, "^/api/settings$", jsonapi_reply_settings_get },
- { EVHTTP_REQ_GET, "^/api/settings/[A-Za-z0-9_]+$", jsonapi_reply_settings_category_get },
- { EVHTTP_REQ_GET, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_get },
- { EVHTTP_REQ_PUT, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_put },
- { EVHTTP_REQ_DELETE, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_delete },
- { EVHTTP_REQ_GET, "^/api/library$", jsonapi_reply_library },
- { EVHTTP_REQ_GET |
- EVHTTP_REQ_PUT, "^/api/update$", jsonapi_reply_update },
- { EVHTTP_REQ_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan },
- { EVHTTP_REQ_GET, "^/api/spotify-logout$", jsonapi_reply_spotify_logout },
- { EVHTTP_REQ_GET, "^/api/spotify$", jsonapi_reply_spotify },
- { EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get },
- { EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_pair },
- { EVHTTP_REQ_POST, "^/api/lastfm-login$", jsonapi_reply_lastfm_login },
- { EVHTTP_REQ_GET, "^/api/lastfm-logout$", jsonapi_reply_lastfm_logout },
- { EVHTTP_REQ_GET, "^/api/lastfm$", jsonapi_reply_lastfm },
- { EVHTTP_REQ_POST, "^/api/verification$", jsonapi_reply_verification },
+ { HTTPD_METHOD_GET, "^/api/config$", jsonapi_reply_config },
+ { HTTPD_METHOD_GET, "^/api/settings$", jsonapi_reply_settings_get },
+ { HTTPD_METHOD_GET, "^/api/settings/[A-Za-z0-9_]+$", jsonapi_reply_settings_category_get },
+ { HTTPD_METHOD_GET, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_get },
+ { HTTPD_METHOD_PUT, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_put },
+ { HTTPD_METHOD_DELETE, "^/api/settings/[A-Za-z0-9_]+/[A-Za-z0-9_]+$", jsonapi_reply_settings_option_delete },
+ { HTTPD_METHOD_GET, "^/api/library$", jsonapi_reply_library },
+ { HTTPD_METHOD_GET |
+ HTTPD_METHOD_PUT, "^/api/update$", jsonapi_reply_update },
+ { HTTPD_METHOD_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan },
+ { HTTPD_METHOD_GET, "^/api/spotify-logout$", jsonapi_reply_spotify_logout },
+ { HTTPD_METHOD_GET, "^/api/spotify$", jsonapi_reply_spotify },
+ { HTTPD_METHOD_GET, "^/api/pairing$", jsonapi_reply_pairing_get },
+ { HTTPD_METHOD_POST, "^/api/pairing$", jsonapi_reply_pairing_pair },
+ { HTTPD_METHOD_POST, "^/api/lastfm-login$", jsonapi_reply_lastfm_login },
+ { HTTPD_METHOD_GET, "^/api/lastfm-logout$", jsonapi_reply_lastfm_logout },
+ { HTTPD_METHOD_GET, "^/api/lastfm$", jsonapi_reply_lastfm },
+ { HTTPD_METHOD_POST, "^/api/verification$", jsonapi_reply_verification },
- { EVHTTP_REQ_GET, "^/api/outputs$", jsonapi_reply_outputs },
- { EVHTTP_REQ_PUT, "^/api/outputs/set$", jsonapi_reply_outputs_set },
- { EVHTTP_REQ_POST, "^/api/select-outputs$", jsonapi_reply_outputs_set }, // deprecated: use "/api/outputs/set"
- { EVHTTP_REQ_GET, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_get_byid },
- { EVHTTP_REQ_PUT, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_put_byid },
- { EVHTTP_REQ_PUT, "^/api/outputs/[[:digit:]]+/toggle$", jsonapi_reply_outputs_toggle_byid },
+ { HTTPD_METHOD_GET, "^/api/outputs$", jsonapi_reply_outputs },
+ { HTTPD_METHOD_PUT, "^/api/outputs/set$", jsonapi_reply_outputs_set },
+ { HTTPD_METHOD_POST, "^/api/select-outputs$", jsonapi_reply_outputs_set }, // deprecated: use "/api/outputs/set"
+ { HTTPD_METHOD_GET, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_get_byid },
+ { HTTPD_METHOD_PUT, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_put_byid },
+ { HTTPD_METHOD_PUT, "^/api/outputs/[[:digit:]]+/toggle$", jsonapi_reply_outputs_toggle_byid },
- { EVHTTP_REQ_GET, "^/api/player$", jsonapi_reply_player },
- { EVHTTP_REQ_PUT, "^/api/player/play$", jsonapi_reply_player_play },
- { EVHTTP_REQ_PUT, "^/api/player/pause$", jsonapi_reply_player_pause },
- { EVHTTP_REQ_PUT, "^/api/player/stop$", jsonapi_reply_player_stop },
- { EVHTTP_REQ_PUT, "^/api/player/toggle$", jsonapi_reply_player_toggle },
- { EVHTTP_REQ_PUT, "^/api/player/next$", jsonapi_reply_player_next },
- { EVHTTP_REQ_PUT, "^/api/player/previous$", jsonapi_reply_player_previous },
- { EVHTTP_REQ_PUT, "^/api/player/shuffle$", jsonapi_reply_player_shuffle },
- { EVHTTP_REQ_PUT, "^/api/player/repeat$", jsonapi_reply_player_repeat },
- { EVHTTP_REQ_PUT, "^/api/player/consume$", jsonapi_reply_player_consume },
- { EVHTTP_REQ_PUT, "^/api/player/volume$", jsonapi_reply_player_volume },
- { EVHTTP_REQ_PUT, "^/api/player/seek$", jsonapi_reply_player_seek },
+ { HTTPD_METHOD_GET, "^/api/player$", jsonapi_reply_player },
+ { HTTPD_METHOD_PUT, "^/api/player/play$", jsonapi_reply_player_play },
+ { HTTPD_METHOD_PUT, "^/api/player/pause$", jsonapi_reply_player_pause },
+ { HTTPD_METHOD_PUT, "^/api/player/stop$", jsonapi_reply_player_stop },
+ { HTTPD_METHOD_PUT, "^/api/player/toggle$", jsonapi_reply_player_toggle },
+ { HTTPD_METHOD_PUT, "^/api/player/next$", jsonapi_reply_player_next },
+ { HTTPD_METHOD_PUT, "^/api/player/previous$", jsonapi_reply_player_previous },
+ { HTTPD_METHOD_PUT, "^/api/player/shuffle$", jsonapi_reply_player_shuffle },
+ { HTTPD_METHOD_PUT, "^/api/player/repeat$", jsonapi_reply_player_repeat },
+ { HTTPD_METHOD_PUT, "^/api/player/consume$", jsonapi_reply_player_consume },
+ { HTTPD_METHOD_PUT, "^/api/player/volume$", jsonapi_reply_player_volume },
+ { HTTPD_METHOD_PUT, "^/api/player/seek$", jsonapi_reply_player_seek },
- { EVHTTP_REQ_GET, "^/api/queue$", jsonapi_reply_queue },
- { EVHTTP_REQ_PUT, "^/api/queue/clear$", jsonapi_reply_queue_clear },
- { EVHTTP_REQ_POST, "^/api/queue/items/add$", jsonapi_reply_queue_tracks_add },
- { EVHTTP_REQ_PUT, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_update },
- { EVHTTP_REQ_PUT, "^/api/queue/items/now_playing$", jsonapi_reply_queue_tracks_update },
- { EVHTTP_REQ_DELETE, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_delete },
- { EVHTTP_REQ_POST, "^/api/queue/save$", jsonapi_reply_queue_save},
+ { HTTPD_METHOD_GET, "^/api/queue$", jsonapi_reply_queue },
+ { HTTPD_METHOD_PUT, "^/api/queue/clear$", jsonapi_reply_queue_clear },
+ { HTTPD_METHOD_POST, "^/api/queue/items/add$", jsonapi_reply_queue_tracks_add },
+ { HTTPD_METHOD_PUT, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_update },
+ { HTTPD_METHOD_PUT, "^/api/queue/items/now_playing$", jsonapi_reply_queue_tracks_update },
+ { HTTPD_METHOD_DELETE, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_delete },
+ { HTTPD_METHOD_POST, "^/api/queue/save$", jsonapi_reply_queue_save},
- { EVHTTP_REQ_GET, "^/api/library/playlists$", jsonapi_reply_library_playlists },
- { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_get },
- { EVHTTP_REQ_PUT, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_put },
- { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlist_tracks },
- { EVHTTP_REQ_PUT, "^/api/library/playlists/[[:digit:]]+/tracks", jsonapi_reply_library_playlist_tracks_put_byid},
-// { EVHTTP_REQ_POST, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlists_tracks },
- { EVHTTP_REQ_DELETE, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_delete },
- { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+/playlists", jsonapi_reply_library_playlist_playlists },
- { EVHTTP_REQ_GET, "^/api/library/artists$", jsonapi_reply_library_artists },
- { EVHTTP_REQ_GET, "^/api/library/artists/[[:digit:]]+$", jsonapi_reply_library_artist },
- { EVHTTP_REQ_GET, "^/api/library/artists/[[:digit:]]+/albums$", jsonapi_reply_library_artist_albums },
- { EVHTTP_REQ_GET, "^/api/library/albums$", jsonapi_reply_library_albums },
- { EVHTTP_REQ_GET, "^/api/library/albums/[[:digit:]]+$", jsonapi_reply_library_album },
- { EVHTTP_REQ_GET, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks },
- { EVHTTP_REQ_PUT, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks_put_byid },
- { EVHTTP_REQ_PUT, "^/api/library/tracks$", jsonapi_reply_library_tracks_put },
- { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_get_byid },
- { EVHTTP_REQ_PUT, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_put_byid },
- { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+/playlists$", jsonapi_reply_library_track_playlists },
- { EVHTTP_REQ_GET, "^/api/library/(genres|composers)$", jsonapi_reply_library_browse },
- { EVHTTP_REQ_GET, "^/api/library/(genres|composers)/.*$", jsonapi_reply_library_browseitem },
- { EVHTTP_REQ_GET, "^/api/library/count$", jsonapi_reply_library_count },
- { EVHTTP_REQ_GET, "^/api/library/files$", jsonapi_reply_library_files },
- { EVHTTP_REQ_POST, "^/api/library/add$", jsonapi_reply_library_add },
- { EVHTTP_REQ_PUT, "^/api/library/backup$", jsonapi_reply_library_backup },
+ { HTTPD_METHOD_GET, "^/api/library/playlists$", jsonapi_reply_library_playlists },
+ { HTTPD_METHOD_GET, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_get },
+ { HTTPD_METHOD_PUT, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_put },
+ { HTTPD_METHOD_GET, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlist_tracks },
+ { HTTPD_METHOD_PUT, "^/api/library/playlists/[[:digit:]]+/tracks", jsonapi_reply_library_playlist_tracks_put_byid},
+// { HTTPD_METHOD_POST, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlists_tracks },
+ { HTTPD_METHOD_DELETE, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_delete },
+ { HTTPD_METHOD_GET, "^/api/library/playlists/[[:digit:]]+/playlists", jsonapi_reply_library_playlist_playlists },
+ { HTTPD_METHOD_GET, "^/api/library/artists$", jsonapi_reply_library_artists },
+ { HTTPD_METHOD_GET, "^/api/library/artists/[[:digit:]]+$", jsonapi_reply_library_artist },
+ { HTTPD_METHOD_GET, "^/api/library/artists/[[:digit:]]+/albums$", jsonapi_reply_library_artist_albums },
+ { HTTPD_METHOD_GET, "^/api/library/albums$", jsonapi_reply_library_albums },
+ { HTTPD_METHOD_GET, "^/api/library/albums/[[:digit:]]+$", jsonapi_reply_library_album },
+ { HTTPD_METHOD_GET, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks },
+ { HTTPD_METHOD_PUT, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks_put_byid },
+ { HTTPD_METHOD_PUT, "^/api/library/tracks$", jsonapi_reply_library_tracks_put },
+ { HTTPD_METHOD_GET, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_get_byid },
+ { HTTPD_METHOD_PUT, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_put_byid },
+ { HTTPD_METHOD_GET, "^/api/library/tracks/[[:digit:]]+/playlists$", jsonapi_reply_library_track_playlists },
+ { HTTPD_METHOD_GET, "^/api/library/(genres|composers)$", jsonapi_reply_library_browse },
+ { HTTPD_METHOD_GET, "^/api/library/(genres|composers)/.*$", jsonapi_reply_library_browseitem },
+ { HTTPD_METHOD_GET, "^/api/library/count$", jsonapi_reply_library_count },
+ { HTTPD_METHOD_GET, "^/api/library/files$", jsonapi_reply_library_files },
+ { HTTPD_METHOD_POST, "^/api/library/add$", jsonapi_reply_library_add },
+ { HTTPD_METHOD_PUT, "^/api/library/backup$", jsonapi_reply_library_backup },
- { EVHTTP_REQ_GET, "^/api/search$", jsonapi_reply_search },
+ { HTTPD_METHOD_GET, "^/api/search$", jsonapi_reply_search },
{ 0, NULL, NULL }
};
@@ -4716,100 +4701,63 @@ static struct httpd_uri_map adm_handlers[] =
/* ------------------------------- JSON API --------------------------------- */
-void
-jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static void
+jsonapi_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
- struct evkeyvalq *headers;
int status_code;
- DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri);
-
- if (!httpd_admin_check_auth(req))
- return;
-
- hreq = httpd_request_parse(req, uri_parsed, NULL, adm_handlers);
- if (!hreq)
+ if (!httpd_admin_check_auth(hreq))
{
- DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in JSON api request: '%s'\n", uri_parsed->path, uri_parsed->uri);
-
- httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
- CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
+ if (!hreq->handler)
+ {
+ DPRINTF(E_LOG, L_WEB, "Unrecognized JSON API request: '%s'\n", hreq->uri);
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
+ return;
+ }
status_code = hreq->handler(hreq);
if (status_code >= 400)
- DPRINTF(E_LOG, L_WEB, "JSON api request failed with error code %d (%s)\n", status_code, uri_parsed->uri);
+ DPRINTF(E_LOG, L_WEB, "JSON api request failed with error code %d (%s)\n", status_code, hreq->uri);
switch (status_code)
{
case HTTP_OK: /* 200 OK */
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "Content-Type", "application/json");
- httpd_send_reply(req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_header_add(hreq->out_headers, "Content-Type", "application/json");
+ httpd_send_reply(hreq, status_code, "OK", HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOCONTENT: /* 204 No Content */
- httpd_send_reply(req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, status_code, "No Content", HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOTMODIFIED: /* 304 Not Modified */
- httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
+ httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, HTTPD_SEND_NO_GZIP);
break;
-
case HTTP_BADREQUEST: /* 400 Bad Request */
- httpd_send_error(req, status_code, "Bad Request");
+ httpd_send_error(hreq, status_code, "Bad Request");
break;
case 403:
- httpd_send_error(req, status_code, "Forbidden");
+ httpd_send_error(hreq, status_code, "Forbidden");
break;
case HTTP_NOTFOUND: /* 404 Not Found */
- httpd_send_error(req, status_code, "Not Found");
+ httpd_send_error(hreq, status_code, "Not Found");
break;
case HTTP_SERVUNAVAIL: /* 503 */
- httpd_send_error(req, status_code, "Service Unavailable");
+ httpd_send_error(hreq, status_code, "Service Unavailable");
break;
case HTTP_INTERNAL: /* 500 Internal Server Error */
default:
- httpd_send_error(req, HTTP_INTERNAL, "Internal Server Error");
+ httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
break;
}
-
- evbuffer_free(hreq->reply);
- free(hreq);
}
-int
-jsonapi_is_request(const char *path)
-{
- if (strncmp(path, "/api/", strlen("/api/")) == 0)
- return 1;
- if (strcmp(path, "/api") == 0)
- return 1;
-
- return 0;
-}
-
-int
+static int
jsonapi_init(void)
{
- char buf[64];
char *temp_path;
- int i;
- int ret;
-
- for (i = 0; adm_handlers[i].handler; i++)
- {
- ret = regcomp(&adm_handlers[i].preg, adm_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &adm_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_WEB, "JSON api init failed; regexp error: %s\n", buf);
- return -1;
- }
- }
default_playlist_directory = NULL;
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists");
@@ -4837,13 +4785,20 @@ jsonapi_init(void)
return 0;
}
-void
+static void
jsonapi_deinit(void)
{
- int i;
-
- for (i = 0; adm_handlers[i].handler; i++)
- regfree(&adm_handlers[i].preg);
-
free(default_playlist_directory);
}
+
+struct httpd_module httpd_jsonapi =
+{
+ .name = "JSON API",
+ .type = MODULE_JSONAPI,
+ .logdomain = L_WEB,
+ .subpaths = { "/api/", NULL },
+ .handlers = adm_handlers,
+ .init = jsonapi_init,
+ .deinit = jsonapi_deinit,
+ .request = jsonapi_request,
+};
diff --git a/src/httpd_jsonapi.h b/src/httpd_jsonapi.h
deleted file mode 100644
index e6113ea6..00000000
--- a/src/httpd_jsonapi.h
+++ /dev/null
@@ -1,19 +0,0 @@
-
-#ifndef __HTTPD_JSONAPI_H__
-#define __HTTPD_JSONAPI_H__
-
-#include "httpd.h"
-
-int
-jsonapi_init(void);
-
-void
-jsonapi_deinit(void);
-
-void
-jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-jsonapi_is_request(const char *path);
-
-#endif /* !__HTTPD_JSONAPI_H__ */
diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c
new file mode 100644
index 00000000..6949df8a
--- /dev/null
+++ b/src/httpd_libevhttp.c
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2023 Espen Jürgensen
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include
+#include
+#include
+#include // TAILQ_FOREACH
+#include // listen()
+
+#include
+#include // flags in struct evhttp
+#include
+#include
+#include
+
+#include
+
+#include "misc.h"
+#include "logger.h"
+#include "commands.h"
+#include "httpd_internal.h"
+
+// #define DEBUG_ALLOC 1
+
+#ifdef DEBUG_ALLOC
+static pthread_mutex_t debug_alloc_lck = PTHREAD_MUTEX_INITIALIZER;
+static int debug_alloc_count;
+#endif
+
+struct httpd_uri_parsed
+{
+ struct evhttp_uri *ev_uri;
+ struct evkeyvalq query;
+ char *path;
+ httpd_uri_path_parts path_parts;
+};
+
+struct httpd_server
+{
+ int fd;
+ struct evhttp *evhttp;
+ struct commands_base *cmdbase;
+ httpd_request_cb request_cb;
+ void *request_cb_arg;
+};
+
+struct httpd_reply
+{
+ struct httpd_request *hreq;
+ enum httpd_reply_type type;
+ int code;
+ const char *reason;
+ httpd_connection_chunkcb chunkcb;
+ void *cbarg;
+};
+
+struct httpd_disconnect
+{
+ pthread_mutex_t lock;
+ struct event *ev;
+ httpd_close_cb cb;
+ void *cbarg;
+};
+
+struct httpd_backend_data
+{
+ // Pointer to server instance processing the request
+ struct httpd_server *server;
+ // If caller wants a callback on disconnect
+ struct httpd_disconnect disconnect;
+};
+
+// Forward
+static void
+closecb_worker(evutil_socket_t fd, short event, void *arg);
+
+
+const char *
+httpd_query_value_find(httpd_query *query, const char *key)
+{
+ return evhttp_find_header(query, key);
+}
+
+void
+httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg)
+{
+ struct evkeyval *param;
+
+ TAILQ_FOREACH(param, query, next)
+ {
+ cb(param->key, param->value, arg);
+ }
+}
+
+void
+httpd_query_clear(httpd_query *query)
+{
+ evhttp_clear_headers(query);
+}
+
+const char *
+httpd_header_find(httpd_headers *headers, const char *key)
+{
+ return evhttp_find_header(headers, key);
+}
+
+void
+httpd_header_remove(httpd_headers *headers, const char *key)
+{
+ evhttp_remove_header(headers, key);
+}
+
+void
+httpd_header_add(httpd_headers *headers, const char *key, const char *val)
+{
+ evhttp_add_header(headers, key, val);
+}
+
+void
+httpd_headers_clear(httpd_headers *headers)
+{
+ evhttp_clear_headers(headers);
+}
+
+void
+httpd_request_close_cb_set(struct httpd_request *hreq, httpd_close_cb cb, void *arg)
+{
+ struct httpd_disconnect *disconnect = &hreq->backend_data->disconnect;
+
+ pthread_mutex_lock(&disconnect->lock);
+
+ disconnect->cb = cb;
+ disconnect->cbarg = arg;
+
+ if (hreq->is_async)
+ {
+ if (disconnect->ev)
+ event_free(disconnect->ev);
+
+ if (disconnect->cb)
+ disconnect->ev = event_new(hreq->evbase, -1, 0, closecb_worker, hreq);
+ else
+ disconnect->ev = NULL;
+ }
+
+ pthread_mutex_unlock(&disconnect->lock);
+}
+
+void
+httpd_request_free(struct httpd_request *hreq)
+{
+#ifdef DEBUG_ALLOC
+ pthread_mutex_lock(&debug_alloc_lck);
+ debug_alloc_count--;
+ pthread_mutex_unlock(&debug_alloc_lck);
+ DPRINTF(E_DBG, L_HTTPD, "DEALLOC hreq - count is %d\n", debug_alloc_count);
+#endif
+
+ if (!hreq)
+ return;
+
+ if (hreq->out_body)
+ evbuffer_free(hreq->out_body);
+
+ httpd_uri_parsed_free(hreq->uri_parsed);
+ httpd_backend_data_free(hreq->backend_data);
+ free(hreq);
+}
+
+struct httpd_request *
+httpd_request_new(httpd_backend *backend, httpd_server *server, const char *uri, const char *user_agent)
+{
+ struct httpd_request *hreq;
+ httpd_backend_data *backend_data;
+
+ CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request)));
+
+#ifdef DEBUG_ALLOC
+ pthread_mutex_lock(&debug_alloc_lck);
+ debug_alloc_count++;
+ pthread_mutex_unlock(&debug_alloc_lck);
+ DPRINTF(E_DBG, L_HTTPD, "ALLOC hreq - count is %d\n", debug_alloc_count);
+#endif
+
+ // Populate hreq by getting values from the backend (or from the caller)
+ hreq->backend = backend;
+ if (backend)
+ {
+ backend_data = httpd_backend_data_create(backend, server);
+ hreq->backend_data = backend_data;
+
+ hreq->uri = httpd_backend_uri_get(backend, backend_data);
+ hreq->uri_parsed = httpd_uri_parsed_create(backend);
+
+ hreq->in_headers = httpd_backend_input_headers_get(backend);
+ hreq->out_headers = httpd_backend_output_headers_get(backend);
+ hreq->in_body = httpd_backend_input_buffer_get(backend);
+ httpd_backend_method_get(&hreq->method, backend);
+ httpd_backend_peer_get(&hreq->peer_address, &hreq->peer_port, backend, backend_data);
+
+ hreq->user_agent = httpd_header_find(hreq->in_headers, "User-Agent");
+ }
+ else
+ {
+ hreq->uri = uri;
+ hreq->uri_parsed = httpd_uri_parsed_create_fromuri(uri);
+
+ hreq->user_agent = user_agent;
+ }
+
+ if (!hreq->uri_parsed)
+ {
+ DPRINTF(E_LOG, L_HTTPD, "Unable to parse URI '%s' in request from '%s'\n", hreq->uri, hreq->peer_address);
+ goto error;
+ }
+
+ // Don't write directly to backend's buffer. This way we are sure we own the
+ // buffer even if there is no backend.
+ CHECK_NULL(L_HTTPD, hreq->out_body = evbuffer_new());
+
+ hreq->path = httpd_uri_path_get(hreq->uri_parsed);
+ hreq->query = httpd_uri_query_get(hreq->uri_parsed);
+ httpd_uri_path_parts_get(&hreq->path_parts, hreq->uri_parsed);
+
+ return hreq;
+
+ error:
+ httpd_request_free(hreq);
+ return NULL;
+}
+
+// Since this is async, libevent will already have closed the connection, so
+// the parts of hreq that are from httpd_connection will now be invalid e.g.
+// peer_address.
+static void
+closecb_worker(evutil_socket_t fd, short event, void *arg)
+{
+ struct httpd_request *hreq = arg;
+ struct httpd_disconnect *disconnect = &hreq->backend_data->disconnect;
+
+ pthread_mutex_lock(&disconnect->lock);
+
+ if (disconnect->cb)
+ disconnect->cb(disconnect->cbarg);
+
+ pthread_mutex_unlock(&disconnect->lock);
+
+ httpd_send_reply_end(hreq); // hreq is now deallocated
+}
+
+static void
+closecb_httpd(httpd_connection *conn, void *arg)
+{
+ struct httpd_request *hreq = arg;
+ struct httpd_disconnect *disconnect = &hreq->backend_data->disconnect;
+
+ DPRINTF(E_WARN, hreq->module->logdomain, "Connection to '%s' was closed\n", hreq->peer_address);
+
+ // The disconnect event may occur while a worker thread is accessing hreq, or
+ // has an event scheduled that will do so, so we have to be careful to let it
+ // finish and cancel events.
+ pthread_mutex_lock(&disconnect->lock);
+ if (hreq->is_async)
+ {
+ if (disconnect->cb)
+ event_active(disconnect->ev, 0, 0);
+
+ pthread_mutex_unlock(&disconnect->lock);
+ return;
+ }
+ pthread_mutex_unlock(&disconnect->lock);
+
+ if (!disconnect->cb)
+ return;
+
+ disconnect->cb(disconnect->cbarg);
+ httpd_send_reply_end(hreq); // hreq is now deallocated
+}
+
+static void
+gencb_httpd(httpd_backend *backend, void *arg)
+{
+ httpd_server *server = arg;
+ struct httpd_request *hreq;
+ struct bufferevent *bufev;
+
+ // Clear the proxy request flag set by evhttp if the request URI was absolute.
+ // It has side-effects on Connection: keep-alive
+ backend->flags &= ~EVHTTP_PROXY_REQUEST;
+
+ // This is a workaround for some versions of libevent (2.0 and 2.1) that don't
+ // detect if the client hangs up, and thus don't clean up and never call the
+ // connection close cb(). See github issue #870 and
+ // https://github.com/libevent/libevent/issues/666. It should probably be
+ // removed again in the future.
+ bufev = evhttp_connection_get_bufferevent(evhttp_request_get_connection(backend));
+ if (bufev)
+ bufferevent_enable(bufev, EV_READ);
+
+ hreq = httpd_request_new(backend, server, NULL, NULL);
+ if (!hreq)
+ {
+ evhttp_send_error(backend, HTTP_INTERNAL, "Internal error");
+ return;
+ }
+
+ // We must hook connection close, so we can assure that conn close callbacks
+ // to handlers running in a worker are made in the same thread.
+ evhttp_connection_set_closecb(evhttp_request_get_connection(backend), closecb_httpd, hreq);
+
+ server->request_cb(hreq, server->request_cb_arg);
+}
+
+void
+httpd_server_free(httpd_server *server)
+{
+ if (!server)
+ return;
+
+ if (server->fd > 0)
+ close(server->fd);
+
+ if (server->evhttp)
+ evhttp_free(server->evhttp);
+
+ commands_base_free(server->cmdbase);
+ free(server);
+}
+
+httpd_server *
+httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_cb cb, void *arg)
+{
+ httpd_server *server;
+ int ret;
+
+ CHECK_NULL(L_HTTPD, server = calloc(1, sizeof(httpd_server)));
+ CHECK_NULL(L_HTTPD, server->evhttp = evhttp_new(evbase));
+ CHECK_NULL(L_HTTPD, server->cmdbase = commands_base_new(evbase, NULL));
+
+ server->request_cb = cb;
+ server->request_cb_arg = arg;
+
+ server->fd = net_bind_with_reuseport(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd");
+ if (server->fd <= 0)
+ goto error;
+
+ // Backlog of 128 is the same that libevent uses
+ ret = listen(server->fd, 128);
+ if (ret < 0)
+ goto error;
+
+ ret = evhttp_accept_socket(server->evhttp, server->fd);
+ if (ret < 0)
+ goto error;
+
+ evhttp_set_gencb(server->evhttp, gencb_httpd, server);
+
+ return server;
+
+ error:
+ httpd_server_free(server);
+ return NULL;
+}
+
+void
+httpd_server_allow_origin_set(httpd_server *server, bool allow)
+{
+ evhttp_set_allowed_methods(server->evhttp, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
+}
+
+// No locking of hreq required here, we're in the httpd thread, and the worker
+// thread is waiting at commands_exec_sync()
+static void
+send_reply_and_free(struct httpd_reply *reply)
+{
+ struct httpd_request *hreq = reply->hreq;
+ httpd_connection *conn;
+
+// DPRINTF(E_DBG, L_HTTPD, "Send from httpd thread, type %d, backend %p\n", reply->type, hreq->backend);
+
+ if (reply->type & HTTPD_F_REPLY_LAST)
+ {
+ conn = evhttp_request_get_connection(hreq->backend);
+ if (conn)
+ evhttp_connection_set_closecb(conn, NULL, NULL);
+ }
+
+ switch (reply->type)
+ {
+ case HTTPD_REPLY_COMPLETE:
+ evhttp_send_reply(hreq->backend, reply->code, reply->reason, hreq->out_body);
+ break;
+ case HTTPD_REPLY_START:
+ evhttp_send_reply_start(hreq->backend, reply->code, reply->reason);
+ break;
+ case HTTPD_REPLY_CHUNK:
+ evhttp_send_reply_chunk_with_cb(hreq->backend, hreq->out_body, reply->chunkcb, reply->cbarg);
+ break;
+ case HTTPD_REPLY_END:
+ evhttp_send_reply_end(hreq->backend);
+ break;
+ }
+}
+
+static enum command_state
+send_reply_and_free_cb(void *arg, int *retval)
+{
+ struct httpd_reply *reply = arg;
+
+ send_reply_and_free(reply);
+
+ return COMMAND_END;
+}
+
+void
+httpd_send(struct httpd_request *hreq, enum httpd_reply_type type, int code, const char *reason, httpd_connection_chunkcb cb, void *cbarg)
+{
+ struct httpd_server *server = hreq->backend_data->server;
+ struct httpd_reply reply = {
+ .hreq = hreq,
+ .type = type,
+ .code = code,
+ .chunkcb = cb,
+ .cbarg = cbarg,
+ .reason = reason,
+ };
+
+ if (type & HTTPD_F_REPLY_LAST)
+ httpd_request_close_cb_set(hreq, NULL, NULL);
+
+ // Sending async is not a option, because then the worker thread might touch
+ // hreq before we have completed sending the current chunk
+ if (hreq->is_async)
+ commands_exec_sync(server->cmdbase, send_reply_and_free_cb, NULL, &reply);
+ else
+ send_reply_and_free(&reply);
+
+ if (type & HTTPD_F_REPLY_LAST)
+ httpd_request_free(hreq);
+}
+
+httpd_backend_data *
+httpd_backend_data_create(httpd_backend *backend, httpd_server *server)
+{
+ httpd_backend_data *backend_data;
+
+ CHECK_NULL(L_HTTPD, backend_data = calloc(1, sizeof(httpd_backend_data)));
+ CHECK_ERR(L_HTTPD, mutex_init(&backend_data->disconnect.lock));
+ backend_data->server = server;
+
+ return backend_data;
+}
+
+void
+httpd_backend_data_free(httpd_backend_data *backend_data)
+{
+ if (!backend_data)
+ return;
+
+ if (backend_data->disconnect.ev)
+ event_free(backend_data->disconnect.ev);
+
+ free(backend_data);
+}
+
+struct event_base *
+httpd_backend_evbase_get(httpd_backend *backend)
+{
+ httpd_connection *conn = evhttp_request_get_connection(backend);
+ if (!conn)
+ return NULL;
+
+ return evhttp_connection_get_base(conn);
+}
+
+const char *
+httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data)
+{
+ return evhttp_request_get_uri(backend);
+}
+
+httpd_headers *
+httpd_backend_input_headers_get(httpd_backend *backend)
+{
+ return evhttp_request_get_input_headers(backend);
+}
+
+httpd_headers *
+httpd_backend_output_headers_get(httpd_backend *backend)
+{
+ return evhttp_request_get_output_headers(backend);
+}
+
+struct evbuffer *
+httpd_backend_input_buffer_get(httpd_backend *backend)
+{
+ return evhttp_request_get_input_buffer(backend);
+}
+
+struct evbuffer *
+httpd_backend_output_buffer_get(httpd_backend *backend)
+{
+ return evhttp_request_get_output_buffer(backend);
+}
+
+int
+httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data)
+{
+ httpd_connection *conn = evhttp_request_get_connection(backend);
+ if (!conn)
+ return -1;
+
+#ifdef HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR
+ evhttp_connection_get_peer(conn, addr, port);
+#else
+ evhttp_connection_get_peer(conn, (char **)addr, port);
+#endif
+ return 0;
+}
+
+int
+httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend)
+{
+ enum evhttp_cmd_type cmd = evhttp_request_get_command(backend);
+
+ switch (cmd)
+ {
+ case EVHTTP_REQ_GET: *method = HTTPD_METHOD_GET; break;
+ case EVHTTP_REQ_POST: *method = HTTPD_METHOD_POST; break;
+ case EVHTTP_REQ_HEAD: *method = HTTPD_METHOD_HEAD; break;
+ case EVHTTP_REQ_PUT: *method = HTTPD_METHOD_PUT; break;
+ case EVHTTP_REQ_DELETE: *method = HTTPD_METHOD_DELETE; break;
+ case EVHTTP_REQ_OPTIONS: *method = HTTPD_METHOD_OPTIONS; break;
+ case EVHTTP_REQ_TRACE: *method = HTTPD_METHOD_TRACE; break;
+ case EVHTTP_REQ_CONNECT: *method = HTTPD_METHOD_CONNECT; break;
+ case EVHTTP_REQ_PATCH: *method = HTTPD_METHOD_PATCH; break;
+ default: *method = HTTPD_METHOD_GET; return -1;
+ }
+
+ return 0;
+}
+
+httpd_uri_parsed *
+httpd_uri_parsed_create(httpd_backend *backend)
+{
+ const char *uri = evhttp_request_get_uri(backend);
+
+ return httpd_uri_parsed_create_fromuri(uri);
+}
+
+httpd_uri_parsed *
+httpd_uri_parsed_create_fromuri(const char *uri)
+{
+ struct httpd_uri_parsed *parsed;
+ const char *query;
+ char *path = NULL;
+ char *path_part;
+ char *ptr;
+ int i;
+
+ parsed = calloc(1, sizeof(struct httpd_uri_parsed));
+ if (!parsed)
+ goto error;
+
+ parsed->ev_uri = evhttp_uri_parse_with_flags(uri, EVHTTP_URI_NONCONFORMANT);
+ if (!parsed->ev_uri)
+ goto error;
+
+ query = evhttp_uri_get_query(parsed->ev_uri);
+ if (query && strchr(query, '=') && evhttp_parse_query_str(query, &(parsed->query)) < 0)
+ goto error;
+
+ path = strdup(evhttp_uri_get_path(parsed->ev_uri));
+ if (!path || !(parsed->path = evhttp_uridecode(path, 0, NULL)))
+ goto error;
+
+ path_part = strtok_r(path, "/", &ptr);
+ for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
+ {
+ parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
+ path_part = strtok_r(NULL, "/", &ptr);
+ }
+
+ // If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
+ if (path_part)
+ goto error;
+
+ free(path);
+ return parsed;
+
+ error:
+ free(path);
+ httpd_uri_parsed_free(parsed);
+ return NULL;
+}
+
+void
+httpd_uri_parsed_free(httpd_uri_parsed *parsed)
+{
+ int i;
+
+ if (!parsed)
+ return;
+
+ free(parsed->path);
+ for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
+ free(parsed->path_parts[i]);
+
+ httpd_query_clear(&(parsed->query));
+
+ if (parsed->ev_uri)
+ evhttp_uri_free(parsed->ev_uri);
+
+ free(parsed);
+}
+
+httpd_query *
+httpd_uri_query_get(httpd_uri_parsed *parsed)
+{
+ return &parsed->query;
+}
+
+const char *
+httpd_uri_path_get(httpd_uri_parsed *parsed)
+{
+ return parsed->path;
+}
+
+void
+httpd_uri_path_parts_get(httpd_uri_path_parts *path_parts, httpd_uri_parsed *parsed)
+{
+ memcpy(path_parts, parsed->path_parts, sizeof(httpd_uri_path_parts));
+}
diff --git a/src/httpd_oauth.c b/src/httpd_oauth.c
index 202b2633..951aef49 100644
--- a/src/httpd_oauth.c
+++ b/src/httpd_oauth.c
@@ -28,7 +28,7 @@
#include
#include
-#include "httpd_oauth.h"
+#include "httpd_internal.h"
#include "logger.h"
#include "misc.h"
#include "conffile.h"
@@ -54,12 +54,12 @@ oauth_reply_spotify(struct httpd_request *hreq)
ret = spotifywebapi_oauth_callback(hreq->query, redirect_uri, &errmsg);
if (ret < 0)
{
- DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri_parsed->uri, errmsg);
- httpd_send_error(hreq->req, HTTP_INTERNAL, errmsg);
+ DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri, errmsg);
+ httpd_send_error(hreq, HTTP_INTERNAL, errmsg);
return -1;
}
- httpd_redirect_to(hreq->req, "/#/settings/online-services");
+ httpd_redirect_to(hreq, "/#/settings/online-services");
return 0;
}
@@ -69,7 +69,7 @@ oauth_reply_spotify(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_WEB, "This version was built without support for Spotify\n");
- httpd_send_error(hreq->req, HTTP_NOTFOUND, "This version was built without support for Spotify");
+ httpd_send_error(hreq, HTTP_NOTFOUND, "This version was built without support for Spotify");
return -1;
}
@@ -90,65 +90,27 @@ static struct httpd_uri_map oauth_handlers[] =
/* ------------------------------- OAUTH API -------------------------------- */
-void
-oauth_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static void
+oauth_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
-
- DPRINTF(E_LOG, L_WEB, "OAuth request: '%s'\n", uri_parsed->uri);
-
- hreq = httpd_request_parse(req, uri_parsed, NULL, oauth_handlers);
- if (!hreq)
+ if (!hreq->handler)
{
- DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in OAuth request: '%s'\n", uri_parsed->path, uri_parsed->uri);
+ DPRINTF(E_LOG, L_WEB, "Unrecognized path in OAuth request: '%s'\n", hreq->uri);
- httpd_send_error(req, HTTP_NOTFOUND, NULL);
+ httpd_send_error(hreq, HTTP_NOTFOUND, NULL);
return;
}
hreq->handler(hreq);
-
- free(hreq);
}
-int
-oauth_is_request(const char *path)
+struct httpd_module httpd_oauth =
{
- if (strncmp(path, "/oauth/", strlen("/oauth/")) == 0)
- return 1;
- if (strcmp(path, "/oauth") == 0)
- return 1;
-
- return 0;
-}
-
-int
-oauth_init(void)
-{
- char buf[64];
- int i;
- int ret;
-
- for (i = 0; oauth_handlers[i].handler; i++)
- {
- ret = regcomp(&oauth_handlers[i].preg, oauth_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &oauth_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_WEB, "OAuth init failed; regexp error: %s\n", buf);
- return -1;
- }
- }
-
- return 0;
-}
-
-void
-oauth_deinit(void)
-{
- int i;
-
- for (i = 0; oauth_handlers[i].handler; i++)
- regfree(&oauth_handlers[i].preg);
-}
+ .name = "OAuth",
+ .type = MODULE_OAUTH,
+ .logdomain = L_WEB,
+ .subpaths = { "/oauth/", NULL },
+ .fullpaths = { "/oauth", NULL },
+ .handlers = oauth_handlers,
+ .request = oauth_request,
+};
diff --git a/src/httpd_oauth.h b/src/httpd_oauth.h
deleted file mode 100644
index 71ff1495..00000000
--- a/src/httpd_oauth.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __HTTPD_OAUTH_H__
-#define __HTTPD_OAUTH_H__
-
-#include "httpd.h"
-
-void
-oauth_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-oauth_is_request(const char *path);
-
-int
-oauth_init(void);
-
-void
-oauth_deinit(void);
-
-#endif /* !__HTTPD_OAUTH_H__ */
diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c
index 16c4b730..7bfce8f4 100644
--- a/src/httpd_rsp.c
+++ b/src/httpd_rsp.c
@@ -32,12 +32,11 @@
#include "mxml-compat.h"
-#include "httpd_rsp.h"
+#include "httpd_internal.h"
#include "logger.h"
#include "db.h"
#include "conffile.h"
#include "misc.h"
-#include "httpd.h"
#include "transcode.h"
#include "parsers/rsp_parser.h"
@@ -120,28 +119,17 @@ static const struct field_map rsp_fields[] =
/* -------------------------------- HELPERS --------------------------------- */
-static struct evbuffer *
-mxml_to_evbuf(mxml_node_t *tree)
+static int
+mxml_to_evbuf(struct evbuffer *evbuf, mxml_node_t *tree)
{
- struct evbuffer *evbuf;
char *xml;
int ret;
- evbuf = evbuffer_new();
- if (!evbuf)
- {
- DPRINTF(E_LOG, L_RSP, "Could not create evbuffer for RSP reply\n");
-
- return NULL;
- }
-
xml = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);
if (!xml)
{
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
-
- evbuffer_free(evbuf);
- return NULL;
+ return -1;
}
ret = evbuffer_add(evbuf, xml, strlen(xml));
@@ -149,22 +137,19 @@ mxml_to_evbuf(mxml_node_t *tree)
if (ret < 0)
{
DPRINTF(E_LOG, L_RSP, "Could not load evbuffer for RSP reply\n");
-
- evbuffer_free(evbuf);
- return NULL;
+ return -1;
}
- return evbuf;
+ return 0;
}
static void
-rsp_send_error(struct evhttp_request *req, char *errmsg)
+rsp_send_error(struct httpd_request *hreq, char *errmsg)
{
- struct evbuffer *evbuf;
- struct evkeyvalq *headers;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *node;
+ int ret;
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
@@ -187,23 +172,19 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
node = mxmlNewElement(status, "totalrecords");
mxmlNewText(node, 0, "0");
- evbuf = mxml_to_evbuf(reply);
+ ret = mxml_to_evbuf(hreq->out_body, reply);
mxmlDelete(reply);
- if (!evbuf)
+ if (ret < 0)
{
- httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-
+ httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
- evhttp_add_header(headers, "Connection", "close");
+ httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
+ httpd_header_add(hreq->out_headers, "Connection", "close");
- httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
-
- evbuffer_free(evbuf);
+ httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
}
static int
@@ -215,25 +196,25 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
int ret;
qp->offset = 0;
- param = evhttp_find_header(hreq->query, "offset");
+ param = httpd_query_value_find(hreq->query, "offset");
if (param)
{
ret = safe_atoi32(param, &qp->offset);
if (ret < 0)
{
- rsp_send_error(hreq->req, "Invalid offset");
+ rsp_send_error(hreq, "Invalid offset");
return -1;
}
}
qp->limit = 0;
- param = evhttp_find_header(hreq->query, "limit");
+ param = httpd_query_value_find(hreq->query, "limit");
if (param)
{
ret = safe_atoi32(param, &qp->limit);
if (ret < 0)
{
- rsp_send_error(hreq->req, "Invalid limit");
+ rsp_send_error(hreq, "Invalid limit");
return -1;
}
}
@@ -244,7 +225,7 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
qp->idx_type = I_NONE;
qp->filter = NULL;
- param = evhttp_find_header(hreq->query, "query");
+ param = httpd_query_value_find(hreq->query, "query");
if (param)
{
ret = snprintf(query, sizeof(query), "%s", param);
@@ -278,28 +259,23 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
}
static void
-rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
+rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply)
{
- struct evbuffer *evbuf;
- struct evkeyvalq *headers;
+ int ret;
- evbuf = mxml_to_evbuf(reply);
+ ret = mxml_to_evbuf(hreq->out_body, reply);
mxmlDelete(reply);
- if (!evbuf)
+ if (ret < 0)
{
- rsp_send_error(req, "Could not finalize reply");
-
+ rsp_send_error(hreq, "Could not finalize reply");
return;
}
- headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
- evhttp_add_header(headers, "Connection", "close");
+ httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
+ httpd_header_add(hreq->out_headers, "Connection", "close");
- httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
-
- evbuffer_free(evbuf);
+ httpd_send_reply(hreq, HTTP_OK, "OK", 0);
}
static int
@@ -318,7 +294,7 @@ rsp_request_authorize(struct httpd_request *hreq)
DPRINTF(E_DBG, L_RSP, "Checking authentication for library\n");
// We don't care about the username
- ret = httpd_basic_auth(hreq->req, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
+ ret = httpd_basic_auth(hreq, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
if (ret != 0)
{
DPRINTF(E_LOG, L_RSP, "Unsuccessful library authorization attempt from '%s'\n", hreq->peer_address);
@@ -382,7 +358,7 @@ rsp_reply_info(struct httpd_request *hreq)
node = mxmlNewElement(info, "name");
mxmlNewText(node, 0, library);
- rsp_send_reply(hreq->req, reply);
+ rsp_send_reply(hreq, reply);
return 0;
}
@@ -411,7 +387,7 @@ rsp_reply_db(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
- rsp_send_error(hreq->req, "Could not start query");
+ rsp_send_error(hreq, "Could not start query");
return -1;
}
@@ -465,7 +441,7 @@ rsp_reply_db(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
- rsp_send_error(hreq->req, "Error fetching query results");
+ rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@@ -479,7 +455,7 @@ rsp_reply_db(struct httpd_request *hreq)
db_query_end(&qp);
- rsp_send_reply(hreq->req, reply);
+ rsp_send_reply(hreq, reply);
return 0;
}
@@ -489,7 +465,6 @@ rsp_reply_playlist(struct httpd_request *hreq)
{
struct query_params qp;
struct db_media_file_info dbmfi;
- struct evkeyvalq *headers;
const char *param;
const char *ua;
const char *client_codecs;
@@ -508,10 +483,10 @@ rsp_reply_playlist(struct httpd_request *hreq)
memset(&qp, 0, sizeof(struct query_params));
- ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
+ ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0)
{
- rsp_send_error(hreq->req, "Invalid playlist ID");
+ rsp_send_error(hreq, "Invalid playlist ID");
return -1;
}
@@ -523,7 +498,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
qp.sort = S_NAME;
mode = F_FULL;
- param = evhttp_find_header(hreq->query, "type");
+ param = httpd_query_value_find(hreq->query, "type");
if (param)
{
if (strcasecmp(param, "full") == 0)
@@ -547,7 +522,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
- rsp_send_error(hreq->req, "Could not start query");
+ rsp_send_error(hreq, "Could not start query");
if (qp.filter)
free(qp.filter);
@@ -587,10 +562,8 @@ rsp_reply_playlist(struct httpd_request *hreq)
/* Items block (all items) */
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
- headers = evhttp_request_get_input_headers(hreq->req);
-
- ua = evhttp_find_header(headers, "User-Agent");
- client_codecs = evhttp_find_header(headers, "Accept-Codecs");
+ ua = httpd_header_find(hreq->in_headers, "User-Agent");
+ client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
@@ -658,7 +631,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
- rsp_send_error(hreq->req, "Error fetching query results");
+ rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@@ -672,7 +645,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
db_query_end(&qp);
- rsp_send_reply(hreq->req, reply);
+ rsp_send_reply(hreq, reply);
return 0;
}
@@ -691,34 +664,34 @@ rsp_reply_browse(struct httpd_request *hreq)
memset(&qp, 0, sizeof(struct query_params));
- if (strcmp(hreq->uri_parsed->path_parts[3], "artist") == 0)
+ if (strcmp(hreq->path_parts[3], "artist") == 0)
{
qp.type = Q_BROWSE_ARTISTS;
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "genre") == 0)
+ else if (strcmp(hreq->path_parts[3], "genre") == 0)
{
qp.type = Q_BROWSE_GENRES;
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "album") == 0)
+ else if (strcmp(hreq->path_parts[3], "album") == 0)
{
qp.type = Q_BROWSE_ALBUMS;
}
- else if (strcmp(hreq->uri_parsed->path_parts[3], "composer") == 0)
+ else if (strcmp(hreq->path_parts[3], "composer") == 0)
{
qp.type = Q_BROWSE_COMPOSERS;
}
else
{
- DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->uri_parsed->path_parts[3]);
+ DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->path_parts[3]);
- rsp_send_error(hreq->req, "Unsupported browse type");
+ rsp_send_error(hreq, "Unsupported browse type");
return -1;
}
- ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
+ ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0)
{
- rsp_send_error(hreq->req, "Invalid playlist ID");
+ rsp_send_error(hreq, "Invalid playlist ID");
return -1;
}
@@ -731,7 +704,7 @@ rsp_reply_browse(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
- rsp_send_error(hreq->req, "Could not start query");
+ rsp_send_error(hreq, "Could not start query");
if (qp.filter)
free(qp.filter);
@@ -784,7 +757,7 @@ rsp_reply_browse(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
- rsp_send_error(hreq->req, "Error fetching query results");
+ rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@@ -798,7 +771,7 @@ rsp_reply_browse(struct httpd_request *hreq)
db_query_end(&qp);
- rsp_send_reply(hreq->req, reply);
+ rsp_send_reply(hreq, reply);
return 0;
}
@@ -809,14 +782,14 @@ rsp_stream(struct httpd_request *hreq)
int id;
int ret;
- ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &id);
+ ret = safe_atoi32(hreq->path_parts[2], &id);
if (ret < 0)
{
- httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
- httpd_stream_file(hreq->req, id);
+ httpd_stream_file(hreq, id);
return 0;
}
@@ -852,7 +825,7 @@ static struct httpd_uri_map rsp_handlers[] =
},
{
.regexp = "^/rsp/stream/[[:digit:]]+$",
- .handler = rsp_stream
+ .handler = rsp_stream,
},
{
.regexp = NULL,
@@ -863,74 +836,45 @@ static struct httpd_uri_map rsp_handlers[] =
/* -------------------------------- RSP API --------------------------------- */
-void
-rsp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static void
+rsp_request(struct httpd_request *hreq)
{
- struct httpd_request *hreq;
int ret;
- DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", uri_parsed->uri);
-
- hreq = httpd_request_parse(req, uri_parsed, NULL, rsp_handlers);
- if (!hreq)
+ if (!hreq->handler)
{
- DPRINTF(E_LOG, L_RSP, "Unrecognized path '%s' in RSP request: '%s'\n", uri_parsed->path, uri_parsed->uri);
+ DPRINTF(E_LOG, L_RSP, "Unrecognized path in RSP request: '%s'\n", hreq->uri);
- rsp_send_error(req, "Server error");
+ rsp_send_error(hreq, "Server error");
return;
}
ret = rsp_request_authorize(hreq);
if (ret < 0)
{
- rsp_send_error(req, "Access denied");
+ rsp_send_error(hreq, "Access denied");
free(hreq);
return;
}
hreq->handler(hreq);
-
- free(hreq);
}
-int
-rsp_is_request(const char *path)
-{
- if (strncmp(path, "/rsp/", strlen("/rsp/")) == 0)
- return 1;
-
- return 0;
-}
-
-int
+static int
rsp_init(void)
{
- char buf[64];
- int i;
- int ret;
-
snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE);
- for (i = 0; rsp_handlers[i].handler; i++)
- {
- ret = regcomp(&rsp_handlers[i].preg, rsp_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
- if (ret != 0)
- {
- regerror(ret, &rsp_handlers[i].preg, buf, sizeof(buf));
-
- DPRINTF(E_FATAL, L_RSP, "RSP init failed; regexp error: %s\n", buf);
- return -1;
- }
- }
-
return 0;
}
-void
-rsp_deinit(void)
+struct httpd_module httpd_rsp =
{
- int i;
-
- for (i = 0; rsp_handlers[i].handler; i++)
- regfree(&rsp_handlers[i].preg);
-}
+ .name = "RSP",
+ .type = MODULE_RSP,
+ .logdomain = L_RSP,
+ .subpaths = { "/rsp/", NULL },
+ .handlers = rsp_handlers,
+ .init = rsp_init,
+ .request = rsp_request,
+};
diff --git a/src/httpd_rsp.h b/src/httpd_rsp.h
deleted file mode 100644
index 637afcfe..00000000
--- a/src/httpd_rsp.h
+++ /dev/null
@@ -1,19 +0,0 @@
-
-#ifndef __HTTPD_RSP_H__
-#define __HTTPD_RSP_H__
-
-#include "httpd.h"
-
-int
-rsp_init(void);
-
-void
-rsp_deinit(void);
-
-void
-rsp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-rsp_is_request(const char *path);
-
-#endif /* !__HTTPD_RSP_H__ */
diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c
index b078b925..1306e390 100644
--- a/src/httpd_streaming.c
+++ b/src/httpd_streaming.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Espen Jürgensen
+ * Copyright (C) 2023 Espen Jürgensen
*
* 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
@@ -23,265 +23,66 @@
#include
#include
#include
-#include
-#include
-
#include
#include
-#include
+#include
#include
+#include
-#include "httpd_streaming.h"
+#include "httpd_internal.h"
+#include "outputs/streaming.h"
#include "logger.h"
#include "conffile.h"
-#include "transcode.h"
-#include "player.h"
-#include "listener.h"
-#include "db.h"
-/* httpd event base, from httpd.c */
-extern struct event_base *evbase_httpd;
-
-// Seconds between sending silence when player is idle
-// (to prevent client from hanging up)
-#define STREAMING_SILENCE_INTERVAL 1
-// How many bytes we try to read at a time from the httpd pipe
-#define STREAMING_READ_SIZE STOB(352, 16, 2)
-
-#define STREAMING_MP3_SAMPLE_RATE 44100
-#define STREAMING_MP3_BPS 16
-#define STREAMING_MP3_CHANNELS 2
-#define STREAMING_MP3_BIT_RATE 192000
-
-
-// Linked list of mp3 streaming requests
struct streaming_session {
- struct evhttp_request *req;
- struct streaming_session *next;
+ struct httpd_request *hreq;
- bool require_icy; // Client requested icy meta
- size_t bytes_sent; // Audio bytes sent since last metablock
+ int fd;
+ struct event *readev;
+ struct evbuffer *readbuf;
+ size_t bytes_sent;
+
+ bool icy_is_requested;
+ size_t icy_remaining;
};
-static pthread_mutex_t streaming_sessions_lck;
-static struct streaming_session *streaming_sessions;
-// Means we're not able to encode to mp3
-static bool streaming_not_supported;
+static struct media_quality streaming_default_quality = {
+ .sample_rate = 44100,
+ .bits_per_sample = 16,
+ .channels = 2,
+ .bit_rate = 128000,
+};
-// Interval for sending silence when playback is paused
-static struct timeval streaming_silence_tv = { STREAMING_SILENCE_INTERVAL, 0 };
-// Input buffer, output buffer and encoding ctx for transcode
-static struct encode_ctx *streaming_encode_ctx;
-static struct evbuffer *streaming_encoded_data;
-static struct media_quality streaming_quality_in;
-static struct media_quality streaming_quality_out = { STREAMING_MP3_SAMPLE_RATE, STREAMING_MP3_BPS, STREAMING_MP3_CHANNELS, STREAMING_MP3_BIT_RATE };
+/* ------------------------------ ICY metadata -------------------------------*/
-// Used for pushing events and data from the player
-static struct event *streamingev;
-static struct event *metaev;
-static struct player_status streaming_player_status;
-static int streaming_player_changed;
-static int streaming_pipe[2];
-static int streaming_meta[2];
+// To test mp3 and ICY tagm it is good to use:
+// mpv --display-tags=* http://localhost:3689/stream.mp3
#define STREAMING_ICY_METALEN_MAX 4080 // 255*16 incl header/footer (16bytes)
#define STREAMING_ICY_METATITLELEN_MAX 4064 // STREAMING_ICY_METALEN_MAX -16 (not incl header/footer)
-/* As streaming quality goes up, we send more data to the remote client. With a
- * smaller ICY_METAINT value we have to splice metadata more frequently - on
- * some devices with small input buffers, a higher quality stream and low
- * ICY_METAINT can lead to stuttering as observed on a Roku Soundbridge
- */
-#define STREAMING_ICY_METAINT_DEFAULT 16384
-static unsigned short streaming_icy_metaint = STREAMING_ICY_METAINT_DEFAULT;
-static unsigned streaming_icy_clients;
+// As streaming quality goes up, we send more data to the remote client. With a
+// smaller ICY_METAINT value we have to splice metadata more frequently - on
+// some devices with small input buffers, a higher quality stream and low
+// ICY_METAINT can lead to stuttering as observed on a Roku Soundbridge
+static unsigned short streaming_icy_metaint = 16384;
+
+static pthread_mutex_t streaming_metadata_lck;
static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX];
-static void
-streaming_close_cb(struct evhttp_connection *evcon, void *arg)
-{
- struct streaming_session *this;
- struct streaming_session *session;
- struct streaming_session *prev;
- const char *address;
- ev_uint16_t port;
-
- this = (struct streaming_session *)arg;
-
- httpd_peer_get(&address, &port, evcon);
- DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", address, (int)port);
-
- pthread_mutex_lock(&streaming_sessions_lck);
- if (!streaming_sessions)
- {
- // This close comes during deinit() - we don't free `this` since it is
- // already a dangling ptr (free'd in deinit()) at this stage
- pthread_mutex_unlock(&streaming_sessions_lck);
- return;
- }
-
- prev = NULL;
- for (session = streaming_sessions; session; session = session->next)
- {
- if (session->req == this->req)
- break;
-
- prev = session;
- }
-
- if (!session)
- {
- DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", address, (int)port);
- free(this);
- pthread_mutex_unlock(&streaming_sessions_lck);
- return;
- }
-
- if (!prev)
- streaming_sessions = session->next;
- else
- prev->next = session->next;
-
- if (session->require_icy)
- --streaming_icy_clients;
-
- // Valgrind says libevent doesn't free the request on disconnect (even though it owns it - libevent bug?),
- // so we do it with a reply end
- evhttp_send_reply_end(session->req);
- free(session);
-
- if (!streaming_sessions)
- {
- DPRINTF(E_INFO, L_STREAMING, "No more clients, will stop streaming\n");
- event_del(streamingev);
- event_del(metaev);
- }
-
- pthread_mutex_unlock(&streaming_sessions_lck);
-}
-
-static void
-streaming_end(void)
-{
- struct streaming_session *session;
- struct evhttp_connection *evcon;
- const char *address;
- ev_uint16_t port;
-
- pthread_mutex_lock(&streaming_sessions_lck);
- for (session = streaming_sessions; streaming_sessions; session = streaming_sessions)
- {
- evcon = evhttp_request_get_connection(session->req);
- if (evcon)
- {
- evhttp_connection_set_closecb(evcon, NULL, NULL);
- httpd_peer_get(&address, &port, evcon);
- DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", address, (int)port);
- }
- evhttp_send_reply_end(session->req);
-
- streaming_sessions = session->next;
- free(session);
- }
- pthread_mutex_unlock(&streaming_sessions_lck);
-
- event_del(streamingev);
- event_del(metaev);
-}
-
-static void
-streaming_meta_cb(evutil_socket_t fd, short event, void *arg)
-{
- struct media_quality quality;
- struct decode_ctx *decode_ctx;
- int ret;
-
- transcode_encode_cleanup(&streaming_encode_ctx);
-
- ret = read(fd, &quality, sizeof(struct media_quality));
- if (ret != sizeof(struct media_quality))
- goto error;
-
- decode_ctx = NULL;
- if (quality.bits_per_sample == 16)
- decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, &quality);
- else if (quality.bits_per_sample == 24)
- decode_ctx = transcode_decode_setup_raw(XCODE_PCM24, &quality);
- else if (quality.bits_per_sample == 32)
- decode_ctx = transcode_decode_setup_raw(XCODE_PCM32, &quality);
-
- if (!decode_ctx)
- goto error;
-
- streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, &streaming_quality_out, decode_ctx, NULL, 0, 0);
- transcode_decode_cleanup(&decode_ctx);
- if (!streaming_encode_ctx)
- {
- DPRINTF(E_LOG, L_STREAMING, "Will not be able to stream MP3, libav does not support MP3 encoding: %d/%d/%d @ %d\n", streaming_quality_out.sample_rate, streaming_quality_out.bits_per_sample, streaming_quality_out.channels, streaming_quality_out.bit_rate);
- streaming_not_supported = 1;
- streaming_end();
- return;
- }
-
- streaming_quality_in = quality;
- streaming_not_supported = 0;
-
- return;
-
- error:
- DPRINTF(E_LOG, L_STREAMING, "Unknown or unsupported quality of input data (%d/%d/%d), cannot MP3 encode\n", quality.sample_rate, quality.bits_per_sample, quality.channels);
- streaming_not_supported = 1;
- streaming_end();
-}
-
-static int
-encode_buffer(uint8_t *buffer, size_t size)
-{
- transcode_frame *frame;
- int samples;
- int ret;
-
- if (streaming_not_supported)
- {
- DPRINTF(E_LOG, L_STREAMING, "Streaming unsupported\n");
- return -1;
- }
-
- if (streaming_quality_in.channels == 0)
- {
- DPRINTF(E_LOG, L_STREAMING, "Streaming quality is zero (%d/%d/%d)\n", streaming_quality_in.sample_rate, streaming_quality_in.bits_per_sample, streaming_quality_in.channels);
- return -1;
- }
-
- samples = BTOS(size, streaming_quality_in.bits_per_sample, streaming_quality_in.channels);
-
- frame = transcode_frame_new(buffer, size, samples, &streaming_quality_in);
- if (!frame)
- {
- DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
- return -1;
- }
-
- ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
- transcode_frame_free(frame);
-
- return ret;
-}
-
-/* We know that the icymeta is limited to 1+255*16 (ie 4081) bytes so caller must
- * provide a buf of this size to avoid needless mallocs
- *
- * The icy meta block is defined by a single byte indicating how many double byte
- * words used for the actual meta. Unused bytes are null padded
- *
- * https://stackoverflow.com/questions/4911062/pulling-track-info-from-an-audio-stream-using-php/4914538#4914538
- * http://www.smackfu.com/stuff/programming/shoutcast.html
- */
+// We know that the icymeta is limited to 1+255*16 (ie 4081) bytes so caller must
+// provide a buf of this size to avoid needless mallocs
+//
+// The icy meta block is defined by a single byte indicating how many double byte
+// words used for the actual meta. Unused bytes are null padded
+//
+// https://stackoverflow.com/questions/4911062/pulling-track-info-from-an-audio-stream-using-php/4914538#4914538
+// http://www.smackfu.com/stuff/programming/shoutcast.html
static uint8_t *
-streaming_icy_meta_create(uint8_t buf[STREAMING_ICY_METALEN_MAX+1], const char *title, unsigned *buflen)
+icy_meta_create(uint8_t buf[STREAMING_ICY_METALEN_MAX+1], unsigned *buflen, const char *title)
{
unsigned titlelen;
unsigned metalen;
@@ -322,317 +123,203 @@ streaming_icy_meta_create(uint8_t buf[STREAMING_ICY_METALEN_MAX+1], const char *
return buf;
}
-static uint8_t *
-streaming_icy_meta_splice(const uint8_t *data, size_t datalen, off_t offset, size_t *len)
+static void
+icy_meta_splice(struct evbuffer *out, struct evbuffer *in, size_t *icy_remaining)
{
- uint8_t meta[STREAMING_ICY_METALEN_MAX+1]; // Buffer, of max sz, for the created icymeta
- unsigned metalen; // How much of the buffer is in use
- uint8_t *buf; // Client returned buffer; contains the audio (from data) spliced w/meta (from meta)
+ uint8_t meta[STREAMING_ICY_METALEN_MAX + 1];
+ unsigned metalen;
+ size_t buf_remaining;
+ size_t consume;
- if (data == NULL || datalen == 0)
- return NULL;
+ for (buf_remaining = evbuffer_get_length(in); buf_remaining > 0; buf_remaining -= consume)
+ {
+ consume = MIN(*icy_remaining, buf_remaining);
+ evbuffer_remove_buffer(in, out, consume);
+ *icy_remaining -= consume;
+ if (*icy_remaining == 0)
+ {
+ pthread_mutex_lock(&streaming_metadata_lck);
+ icy_meta_create(meta, &metalen, streaming_icy_title);
+ pthread_mutex_unlock(&streaming_metadata_lck);
- memset(meta, 0, sizeof(meta));
- streaming_icy_meta_create(meta, streaming_icy_title, &metalen);
-
- *len = datalen + metalen;
- // DPRINTF(E_DBG, L_STREAMING, "splicing meta, audio block=%d bytes, offset=%d, metalen=%d new buflen=%d\n", datalen, offset, metalen, *len);
- buf = malloc(*len);
- memcpy(buf, data, offset);
- memcpy(buf+offset, &meta[0], metalen);
- memcpy(buf+offset+metalen, data+offset, datalen-offset);
-
- return buf;
+ evbuffer_add(out, meta, metalen);
+ *icy_remaining = streaming_icy_metaint;
+ }
+ }
}
+// Thread: player. TODO Would be nice to avoid the lock. Consider moving all the
+// ICY tag stuff to streaming.c and make a STREAMING_FORMAT_MP3_ICY?
static void
-streaming_player_status_update(void)
+icy_metadata_cb(char *metadata)
{
- struct db_queue_item *queue_item;
- uint32_t prev_id;
-
- prev_id = streaming_player_status.id;
- player_get_status(&streaming_player_status);
-
- if (prev_id == streaming_player_status.id || !streaming_icy_clients)
- {
- return;
- }
-
- queue_item = db_queue_fetch_byfileid(streaming_player_status.id);
- if (!queue_item)
- {
- streaming_icy_title[0] = '\0';
- return;
- }
-
- snprintf(streaming_icy_title, sizeof(streaming_icy_title), "%s - %s", queue_item->title, queue_item->artist);
- free_queue_item(queue_item, 0);
+ pthread_mutex_lock(&streaming_metadata_lck);
+ snprintf(streaming_icy_title, sizeof(streaming_icy_title), "%s", metadata);
+ pthread_mutex_unlock(&streaming_metadata_lck);
}
+
+/* ----------------------------- Session helpers ---------------------------- */
+
static void
-streaming_send_cb(evutil_socket_t fd, short event, void *arg)
+session_free(struct streaming_session *session)
+{
+ if (!session)
+ return;
+
+ if (session->readev)
+ {
+ streaming_session_deregister(session->fd);
+ event_free(session->readev);
+ }
+
+ evbuffer_free(session->readbuf);
+ free(session);
+}
+
+static struct streaming_session *
+session_new(struct httpd_request *hreq, bool icy_is_requested)
{
struct streaming_session *session;
- struct evbuffer *evbuf;
- uint8_t rawbuf[STREAMING_READ_SIZE];
- uint8_t *buf;
- uint8_t *splice_buf = NULL;
- size_t splice_len;
- size_t count;
- int overflow;
+
+ CHECK_NULL(L_STREAMING, session = calloc(1, sizeof(struct streaming_session)));
+ CHECK_NULL(L_STREAMING, session->readbuf = evbuffer_new());
+
+ session->hreq = hreq;
+ session->icy_is_requested = icy_is_requested;
+ session->icy_remaining = streaming_icy_metaint;
+
+ return session;
+}
+
+
+/* ----------------------------- Event callbacks ---------------------------- */
+
+static void
+conn_close_cb(void *arg)
+{
+ struct streaming_session *session = arg;
+
+ session_free(session);
+}
+
+static void
+read_cb(evutil_socket_t fd, short event, void *arg)
+{
+ struct streaming_session *session = arg;
+ struct httpd_request *hreq;
int len;
- int ret;
- // Player wrote data to the pipe (EV_READ)
- if (event & EV_READ)
+ CHECK_NULL(L_STREAMING, hreq = session->hreq);
+
+ len = evbuffer_read(session->readbuf, fd, -1);
+ if (len < 0 && errno != EAGAIN)
{
- while (1)
- {
- ret = read(fd, &rawbuf, sizeof(rawbuf));
- if (ret <= 0)
- break;
+ DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port);
- if (streaming_player_changed)
- {
- streaming_player_changed = 0;
- streaming_player_status_update();
- }
-
- ret = encode_buffer(rawbuf, ret);
- if (ret < 0)
- return;
- }
+ httpd_send_reply_end(session->hreq);
+ session_free(session);
+ return;
}
- // Event timed out, let's see what the player is doing and send silence if it is paused
+
+ if (session->icy_is_requested)
+ icy_meta_splice(hreq->out_body, session->readbuf, &session->icy_remaining);
else
- {
- if (streaming_player_changed)
- {
- streaming_player_changed = 0;
- streaming_player_status_update();
- }
+ evbuffer_add_buffer(hreq->out_body, session->readbuf);
- if (streaming_player_status.status != PLAY_PAUSED)
- return;
+ httpd_send_reply_chunk(hreq, NULL, NULL);
- memset(&rawbuf, 0, sizeof(rawbuf));
- ret = encode_buffer(rawbuf, sizeof(rawbuf));
- if (ret < 0)
- return;
- }
-
- len = evbuffer_get_length(streaming_encoded_data);
- if (len == 0)
- return;
-
- // Send data
- evbuf = evbuffer_new();
- pthread_mutex_lock(&streaming_sessions_lck);
- for (session = streaming_sessions; session; session = session->next)
- {
- // Does this session want ICY meta data and is it time to send?
- count = session->bytes_sent+len;
- if (session->require_icy && count > streaming_icy_metaint)
- {
- overflow = count%streaming_icy_metaint;
- buf = evbuffer_pullup(streaming_encoded_data, -1);
-
- // DPRINTF(E_DBG, L_STREAMING, "session=%x sent=%ld len=%ld overflow=%ld\n", session, session->bytes_sent, len, overflow);
-
- // Splice the 'icy title' in with the encoded audio data
- splice_len = 0;
- splice_buf = streaming_icy_meta_splice(buf, len, len-overflow, &splice_len);
-
- evbuffer_add(evbuf, splice_buf, splice_len);
-
- free(splice_buf);
- splice_buf = NULL;
-
- evhttp_send_reply_chunk(session->req, evbuf);
-
- if (session->next == NULL)
- {
- // We're the last session, drop the contents of the encoded buffer
- evbuffer_drain(streaming_encoded_data, len);
- }
- session->bytes_sent = overflow;
- }
- else
- {
- if (session->next)
- {
- buf = evbuffer_pullup(streaming_encoded_data, -1);
- evbuffer_add(evbuf, buf, len);
- evhttp_send_reply_chunk(session->req, evbuf);
- }
- else
- {
- evhttp_send_reply_chunk(session->req, streaming_encoded_data);
- }
- session->bytes_sent += len;
- }
- }
- pthread_mutex_unlock(&streaming_sessions_lck);
-
- evbuffer_free(evbuf);
+ session->bytes_sent += len;
}
-// Thread: player (not fully thread safe, but hey...)
-static void
-player_change_cb(short event_mask)
-{
- streaming_player_changed = 1;
-}
-// Thread: player (also prone to race conditions, mostly during deinit)
-void
-streaming_write(struct output_buffer *obuf)
-{
- int ret;
+/* -------------------------- Module implementation ------------------------- */
- // Explicit no-lock - let the write to pipes fail if during deinit
- if (!streaming_sessions)
- return;
-
- if (!quality_is_equal(&obuf->data[0].quality, &streaming_quality_in))
- {
- ret = write(streaming_meta[1], &obuf->data[0].quality, sizeof(struct media_quality));
- if (ret < 0)
- {
- if (errno == EBADF)
- DPRINTF(E_LOG, L_STREAMING, "streaming pipe already closed\n");
- else
- DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe: %s\n", strerror(errno));
- return;
- }
- }
-
- ret = write(streaming_pipe[1], obuf->data[0].buffer, obuf->data[0].bufsize);
- if (ret < 0)
- {
- if (errno == EAGAIN)
- DPRINTF(E_WARN, L_STREAMING, "Streaming pipe full, skipping write\n");
- else
- {
- if (errno == EBADF)
- DPRINTF(E_LOG, L_STREAMING, "Streaming pipe already closed\n");
- else
- DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe: %s\n", strerror(errno));
- }
- }
-}
-
-int
-streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
+static int
+streaming_mp3_handler(struct httpd_request *hreq)
{
struct streaming_session *session;
- struct evhttp_connection *evcon;
- struct evkeyvalq *output_headers;
- cfg_t *lib;
- const char *name;
- const char *address;
- ev_uint16_t port;
+ const char *name = cfg_getstr(cfg_getsec(cfg, "library"), "name");
const char *param;
- bool require_icy = false;
+ bool icy_is_requested;
char buf[9];
- if (streaming_not_supported)
+ param = httpd_header_find(hreq->in_headers, "Icy-MetaData");
+ icy_is_requested = (param && strcmp(param, "1") == 0);
+ if (icy_is_requested)
{
- DPRINTF(E_LOG, L_STREAMING, "Got MP3 streaming request, but cannot encode to MP3\n");
-
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
- return -1;
+ httpd_header_add(hreq->out_headers, "icy-name", name);
+ snprintf(buf, sizeof(buf), "%d", streaming_icy_metaint);
+ httpd_header_add(hreq->out_headers, "icy-metaint", buf);
}
- evcon = evhttp_request_get_connection(req);
- httpd_peer_get(&address, &port, evcon);
- param = evhttp_find_header( evhttp_request_get_input_headers(req), "Icy-MetaData");
- if (param && strcmp(param, "1") == 0)
- require_icy = true;
-
- DPRINTF(E_INFO, L_STREAMING, "Beginning mp3 streaming (with icy=%d, icy_metaint=%d) to %s:%d\n", require_icy, streaming_icy_metaint, address, (int)port);
-
- lib = cfg_getsec(cfg, "library");
- name = cfg_getstr(lib, "name");
-
- output_headers = evhttp_request_get_output_headers(req);
- evhttp_add_header(output_headers, "Content-Type", "audio/mpeg");
- evhttp_add_header(output_headers, "Server", PACKAGE_NAME "/" VERSION);
- evhttp_add_header(output_headers, "Cache-Control", "no-cache");
- evhttp_add_header(output_headers, "Pragma", "no-cache");
- evhttp_add_header(output_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
- if (require_icy)
- {
- ++streaming_icy_clients;
- evhttp_add_header(output_headers, "icy-name", name);
- snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint);
- evhttp_add_header(output_headers, "icy-metaint", buf);
- }
- evhttp_add_header(output_headers, "Access-Control-Allow-Origin", "*");
- evhttp_add_header(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
-
- evhttp_send_reply_start(req, HTTP_OK, "OK");
-
- session = calloc(1, sizeof(struct streaming_session));
+ session = session_new(hreq, icy_is_requested);
if (!session)
- {
- DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n");
+ return -1;
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
- return -1;
- }
+ // Ask streaming output module for a fd to read mp3 from
+ session->fd = streaming_session_register(STREAMING_FORMAT_MP3, streaming_default_quality);
- pthread_mutex_lock(&streaming_sessions_lck);
+ CHECK_NULL(L_STREAMING, session->readev = event_new(hreq->evbase, session->fd, EV_READ | EV_PERSIST, read_cb, session));
+ event_add(session->readev, NULL);
- if (!streaming_sessions)
- {
- event_add(streamingev, &streaming_silence_tv);
- event_add(metaev, NULL);
- }
+ httpd_request_close_cb_set(hreq, conn_close_cb, session);
- session->req = req;
- session->next = streaming_sessions;
- session->require_icy = require_icy;
- session->bytes_sent = 0;
- streaming_sessions = session;
+ httpd_header_add(hreq->out_headers, "Content-Type", "audio/mpeg");
+ httpd_header_add(hreq->out_headers, "Server", PACKAGE_NAME "/" VERSION);
+ httpd_header_add(hreq->out_headers, "Cache-Control", "no-cache");
+ httpd_header_add(hreq->out_headers, "Pragma", "no-cache");
+ httpd_header_add(hreq->out_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
- pthread_mutex_unlock(&streaming_sessions_lck);
-
- evhttp_connection_set_closecb(evcon, streaming_close_cb, session);
+ httpd_send_reply_start(hreq, HTTP_OK, "OK");
return 0;
}
-int
-streaming_is_request(const char *path)
-{
- char *ptr;
+static struct httpd_uri_map streaming_handlers[] =
+ {
+ {
+ .regexp = "^/stream.mp3$",
+ .handler = streaming_mp3_handler,
+ .flags = HTTPD_HANDLER_REALTIME,
+ },
+ {
+ .regexp = NULL,
+ .handler = NULL
+ }
+ };
- ptr = strrchr(path, '/');
- if (ptr && (strcasecmp(ptr, "/stream.mp3") == 0))
- return 1;
-
- return 0;
-}
-
-int
-streaming_init(void)
+static void
+streaming_request(struct httpd_request *hreq)
{
int ret;
- cfg_t *cfgsec;
+
+ if (!hreq->handler)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Unrecognized path in streaming request: '%s'\n", hreq->uri);
+
+ httpd_send_error(hreq, HTTP_NOTFOUND, NULL);
+ return;
+ }
+
+ ret = hreq->handler(hreq);
+ if (ret < 0)
+ httpd_send_error(hreq, HTTP_INTERNAL, NULL);
+}
+
+static int
+streaming_init(void)
+{
int val;
- cfgsec = cfg_getsec(cfg, "streaming");
-
- val = cfg_getint(cfgsec, "sample_rate");
+ val = cfg_getint(cfg_getsec(cfg, "streaming"), "sample_rate");
// Validate against the variations of libmp3lame's supported sample rates: 32000/44100/48000
if (val % 11025 > 0 && val % 12000 > 0 && val % 8000 > 0)
- DPRINTF(E_LOG, L_STREAMING, "Non standard streaming sample_rate=%d, defaulting\n", val);
+ DPRINTF(E_LOG, L_STREAMING, "Unsupported streaming sample_rate=%d, defaulting\n", val);
else
- streaming_quality_out.sample_rate = val;
+ streaming_default_quality.sample_rate = val;
- val = cfg_getint(cfgsec, "bit_rate");
+ val = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
switch (val)
{
case 64:
@@ -640,107 +327,37 @@ streaming_init(void)
case 128:
case 192:
case 320:
- streaming_quality_out.bit_rate = val*1000;
+ streaming_default_quality.bit_rate = val*1000;
break;
default:
DPRINTF(E_LOG, L_STREAMING, "Unsuppported streaming bit_rate=%d, supports: 64/96/128/192/320, defaulting\n", val);
}
- DPRINTF(E_INFO, L_STREAMING, "Streaming quality: %d/%d/%d @ %dkbps\n", streaming_quality_out.sample_rate, streaming_quality_out.bits_per_sample, streaming_quality_out.channels, streaming_quality_out.bit_rate/1000);
+ DPRINTF(E_INFO, L_STREAMING, "Streaming quality: %d/%d/%d @ %dkbps\n",
+ streaming_default_quality.sample_rate, streaming_default_quality.bits_per_sample,
+ streaming_default_quality.channels, streaming_default_quality.bit_rate/1000);
- val = cfg_getint(cfgsec, "icy_metaint");
+ val = cfg_getint(cfg_getsec(cfg, "streaming"), "icy_metaint");
// Too low a value forces server to send more meta than data
if (val >= 4096 && val <= 131072)
streaming_icy_metaint = val;
else
DPRINTF(E_INFO, L_STREAMING, "Unsupported icy_metaint=%d, supported range: 4096..131072, defaulting to %d\n", val, streaming_icy_metaint);
- ret = mutex_init(&streaming_sessions_lck);
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_STREAMING, "Could not initialize mutex (%d): %s\n", ret, strerror(ret));
- goto error;
- }
-
- // Non-blocking because otherwise httpd and player thread may deadlock
-#ifdef HAVE_PIPE2
- ret = pipe2(streaming_pipe, O_CLOEXEC | O_NONBLOCK);
-#else
- if ( pipe(streaming_pipe) < 0 ||
- fcntl(streaming_pipe[0], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 ||
- fcntl(streaming_pipe[1], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 )
- ret = -1;
- else
- ret = 0;
-#endif
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
- goto error;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(streaming_meta, O_CLOEXEC | O_NONBLOCK);
-#else
- if ( pipe(streaming_meta) < 0 ||
- fcntl(streaming_meta[0], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 ||
- fcntl(streaming_meta[1], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 )
- ret = -1;
- else
- ret = 0;
-#endif
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
- goto error;
- }
-
- // Listen to playback changes so we don't have to poll to check for pausing
- ret = listener_add(player_change_cb, LISTENER_PLAYER);
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_STREAMING, "Could not add listener\n");
- goto error;
- }
-
- // Initialize buffer for encoded mp3 audio and event for pipe reading
- CHECK_NULL(L_STREAMING, streaming_encoded_data = evbuffer_new());
-
- CHECK_NULL(L_STREAMING, streamingev = event_new(evbase_httpd, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL));
- CHECK_NULL(L_STREAMING, metaev = event_new(evbase_httpd, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL));
-
- streaming_icy_clients = 0;
+ CHECK_ERR(L_STREAMING, mutex_init(&streaming_metadata_lck));
+ streaming_metadatacb_register(icy_metadata_cb);
return 0;
-
- error:
- close(streaming_pipe[0]);
- close(streaming_pipe[1]);
- close(streaming_meta[0]);
- close(streaming_meta[1]);
-
- return -1;
}
-void
-streaming_deinit(void)
+struct httpd_module httpd_streaming =
{
- streaming_end();
-
- event_free(metaev);
- event_free(streamingev);
- streamingev = NULL;
-
- listener_remove(player_change_cb);
-
- close(streaming_pipe[0]);
- close(streaming_pipe[1]);
- close(streaming_meta[0]);
- close(streaming_meta[1]);
-
- transcode_encode_cleanup(&streaming_encode_ctx);
- evbuffer_free(streaming_encoded_data);
-
- pthread_mutex_destroy(&streaming_sessions_lck);
-}
+ .name = "Streaming",
+ .type = MODULE_STREAMING,
+ .logdomain = L_STREAMING,
+ .fullpaths = { "/stream.mp3", NULL },
+ .handlers = streaming_handlers,
+ .init = streaming_init,
+ .request = streaming_request,
+};
diff --git a/src/httpd_streaming.h b/src/httpd_streaming.h
deleted file mode 100644
index 3df82023..00000000
--- a/src/httpd_streaming.h
+++ /dev/null
@@ -1,29 +0,0 @@
-
-#ifndef __HTTPD_STREAMING_H__
-#define __HTTPD_STREAMING_H__
-
-#include "httpd.h"
-#include "outputs.h"
-
-/* httpd_streaming takes care of incoming requests to /stream.mp3
- * It will receive decoded audio from the player, and encode it, and
- * stream it to one or more clients. It will not be available
- * if a suitable ffmpeg/libav encoder is not present at runtime.
- */
-
-void
-streaming_write(struct output_buffer *obuf);
-
-int
-streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
-
-int
-streaming_is_request(const char *path);
-
-int
-streaming_init(void);
-
-void
-streaming_deinit(void);
-
-#endif /* !__HTTPD_STREAMING_H__ */
diff --git a/src/main.c b/src/main.c
index 51e2d0ac..d611384e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -47,9 +47,7 @@
#include
#include
-#ifdef HAVE_LIBEVENT_PTHREADS
-# include
-#endif
+#include
#include
#include
#include
@@ -730,9 +728,7 @@ main(int argc, char **argv)
/* Initialize event base (after forking) */
CHECK_NULL(L_MAIN, evbase_main = event_base_new());
-#ifdef HAVE_LIBEVENT_PTHREADS
CHECK_ERR(L_MAIN, evthread_use_pthreads());
-#endif
DPRINTF(E_LOG, L_MAIN, "mDNS init\n");
ret = mdns_init();
diff --git a/src/misc.c b/src/misc.c
index 2a145988..0f7c7d78 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -52,6 +52,8 @@
#include
#include // getifaddrs
+#include // evhttp_bind
+
#include
#include
@@ -276,8 +278,8 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
// If *port is 0 then a random port will be assigned, and *port will be updated
// with the port number
-int
-net_bind(short unsigned *port, int type, const char *log_service_name)
+static int
+net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool reuseport)
{
struct addrinfo hints = { 0 };
struct addrinfo *servinfo;
@@ -315,6 +317,14 @@ net_bind(short unsigned *port, int type, const char *log_service_name)
if (fd < 0)
continue;
+ // Makes us able to attach multiple threads to the same port
+ if (reuseport)
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
+ else
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &no, sizeof(no));
+ if (ret < 0)
+ continue;
+
// TODO libevent sets this, we do the same?
ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
if (ret < 0)
@@ -375,7 +385,19 @@ net_bind(short unsigned *port, int type, const char *log_service_name)
}
int
-net_evhttp_bind(struct evhttp *evhttp, short unsigned port, const char *log_service_name)
+net_bind(short unsigned *port, int type, const char *log_service_name)
+{
+ return net_bind_impl(port, type, log_service_name, false);
+}
+
+int
+net_bind_with_reuseport(short unsigned *port, int type, const char *log_service_name)
+{
+ return net_bind_impl(port, type, log_service_name, true);
+}
+
+int
+net_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name)
{
const char *bind_address;
bool v6_enabled;
diff --git a/src/misc.h b/src/misc.h
index 982462f8..d2716e73 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -14,7 +14,6 @@
#include
#include
-#include
#ifndef SOCK_NONBLOCK
#include
@@ -55,7 +54,13 @@ int
net_bind(short unsigned *port, int type, const char *log_service_name);
int
-net_evhttp_bind(struct evhttp *evhttp, short unsigned port, const char *log_service_name);
+net_bind_with_reuseport(short unsigned *port, int type, const char *log_service_name);
+
+// To avoid polluting namespace too much we don't include event2/http.h here
+struct evhttp;
+
+int
+net_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name);
// Just checks if the protocol is http or https
bool
diff --git a/src/mpd.c b/src/mpd.c
index e31dc392..289d6a16 100644
--- a/src/mpd.c
+++ b/src/mpd.c
@@ -47,7 +47,6 @@
#include "commands.h"
#include "conffile.h"
#include "db.h"
-#include "httpd.h"
#include "library.h"
#include "listener.h"
#include "logger.h"
@@ -4651,7 +4650,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET)
{
DPRINTF(E_LOG, L_MPD, "Unsupported request type for artwork\n");
- httpd_send_error(req, HTTP_BADMETHOD, "Method not allowed");
+ evhttp_send_error(req, HTTP_BADMETHOD, "Method not allowed");
return;
}
@@ -4662,7 +4661,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!decoded)
{
DPRINTF(E_LOG, L_MPD, "Bad artwork request with uri '%s'\n", uri);
- httpd_send_error(req, HTTP_BADREQUEST, 0);
+ evhttp_send_error(req, HTTP_BADREQUEST, 0);
return;
}
@@ -4670,7 +4669,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!path)
{
DPRINTF(E_LOG, L_MPD, "Invalid path from artwork request with uri '%s'\n", uri);
- httpd_send_error(req, HTTP_BADREQUEST, 0);
+ evhttp_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
@@ -4679,7 +4678,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!decoded_path)
{
DPRINTF(E_LOG, L_MPD, "Error decoding path from artwork request with uri '%s'\n", uri);
- httpd_send_error(req, HTTP_BADREQUEST, 0);
+ evhttp_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
@@ -4694,7 +4693,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!itemid)
{
DPRINTF(E_WARN, L_MPD, "No item found for path '%s' from request uri '%s'\n", decoded_path, uri);
- httpd_send_error(req, HTTP_NOTFOUND, "Document was not found");
+ evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
@@ -4704,7 +4703,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!evbuffer)
{
DPRINTF(E_LOG, L_MPD, "Could not allocate an evbuffer for artwork request\n");
- httpd_send_error(req, HTTP_INTERNAL, "Document was not found");
+ evhttp_send_error(req, HTTP_INTERNAL, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
@@ -4713,7 +4712,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
format = artwork_get_item(evbuffer, itemid, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT, 0);
if (format < 0)
{
- httpd_send_error(req, HTTP_NOTFOUND, "Document was not found");
+ evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
}
else
{
@@ -4728,7 +4727,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
break;
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuffer, HTTPD_SEND_NO_GZIP);
+ evhttp_send_reply(req, HTTP_OK, "OK", evbuffer);
}
evbuffer_free(evbuffer);
diff --git a/src/outputs.c b/src/outputs.c
index b3786e01..c4e5ba4b 100644
--- a/src/outputs.c
+++ b/src/outputs.c
@@ -467,6 +467,16 @@ metadata_cb_prepare(void *arg)
event_active(metadata->ev, 0, 0);
}
+static void
+metadata_free(struct output_metadata *metadata)
+{
+ if (!metadata)
+ return;
+ if (metadata->ev)
+ event_free(metadata->ev);
+ free(metadata);
+}
+
static void
metadata_send(enum output_types type, uint32_t item_id, bool startup, output_metadata_finalize_cb cb)
{
@@ -689,6 +699,11 @@ outputs_cb(int callback_id, uint64_t device_id, enum output_device_state state)
event_active(outputs_deferredev, 0, 0);
}
+void
+outputs_metadata_free(struct output_metadata *metadata)
+{
+ metadata_free(metadata);
+}
/* ---------------------------- Called by player ---------------------------- */
diff --git a/src/outputs.h b/src/outputs.h
index 7997a4e7..434d88ad 100644
--- a/src/outputs.h
+++ b/src/outputs.h
@@ -252,7 +252,7 @@ struct output_definition
// Called from worker thread for async preparation of metadata (e.g. getting
// artwork, which might involce downloading image data). The prepared data is
- // saved to metadata->data, which metadata_send() can use.
+ // saved to metadata->priv, which metadata_send() can use.
void *(*metadata_prepare)(struct output_metadata *metadata);
// Send metadata to outputs. Ownership of *metadata is transferred.
@@ -284,6 +284,9 @@ outputs_quality_unsubscribe(struct media_quality *quality);
void
outputs_cb(int callback_id, uint64_t device_id, enum output_device_state);
+void
+outputs_metadata_free(struct output_metadata *metadata);
+
/* ---------------------------- Called by player ---------------------------- */
// Ownership of *add is transferred, so don't address after calling. Instead you
diff --git a/src/outputs/streaming.c b/src/outputs/streaming.c
index a6d02413..d1b34d00 100644
--- a/src/outputs/streaming.c
+++ b/src/outputs/streaming.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Espen Jürgensen
+ * Copyright (C) 2023 Espen Jürgensen
*
* 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
@@ -16,13 +16,593 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
#include
#include
-#include
+#include
#include
+#include
+#include
+#include
+#include "streaming.h"
#include "outputs.h"
-#include "httpd_streaming.h"
+#include "misc.h"
+#include "worker.h"
+#include "transcode.h"
+#include "logger.h"
+#include "db.h"
+
+/* About
+ *
+ * This output takes the writes from the player thread, gives them to a worker
+ * thread for mp3 encoding, and then the mp3 is written to a fd for the httpd
+ * request handler to read and pass to clients. If there is no writing from the
+ * player, but there are clients, it instead writes silence to the fd.
+ */
+
+// Seconds between sending silence when player is idle
+// (to prevent client from hanging up)
+#define STREAMING_SILENCE_INTERVAL 1
+
+// How many bytes of silence we encode with the above interval. There is no
+// particular reason for using this size, just that it seems to have worked for
+// a while.
+#define SILENCE_BUF_SIZE STOB(352, 16, 2)
+
+// The wanted structure represents a particular format and quality that should
+// be produced for one or more sessions. A pipe pair is created for each session
+// for the i/o.
+#define WANTED_PIPES_MAX 8
+
+struct pipepair
+{
+ int writefd;
+ int readfd;
+};
+
+struct streaming_wanted
+{
+ int refcount;
+ struct pipepair pipes[WANTED_PIPES_MAX];
+
+ enum streaming_format format;
+ struct media_quality quality_in;
+ struct media_quality quality_out;
+
+ struct encode_ctx *xcode_ctx;
+ struct evbuffer *encoded_data;
+
+ struct streaming_wanted *next;
+};
+
+struct streaming_ctx
+{
+ struct streaming_wanted *wanted;
+ struct event *silenceev;
+ struct timeval silencetv;
+ struct media_quality last_quality;
+
+ // seqnum may wrap around so must be unsigned
+ unsigned int seqnum;
+ unsigned int seqnum_encode_next;
+
+ // callback with new metadata, e.g. for ICY tags
+ void (*metadatacb)(char *metadata);
+};
+
+struct encode_cmdarg
+{
+ uint8_t *buf;
+ size_t bufsize;
+ int samples;
+ unsigned int seqnum;
+ struct media_quality quality;
+};
+
+static pthread_mutex_t streaming_wanted_lck;
+static pthread_cond_t streaming_sequence_cond;
+
+static struct streaming_ctx streaming =
+{
+ .silencetv = { STREAMING_SILENCE_INTERVAL, 0 },
+};
+
+extern struct event_base *evbase_player;
+
+
+/* ------------------------------- Helpers ---------------------------------- */
+
+static int
+pipe_open(struct pipepair *p)
+{
+ int fd[2];
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC | O_NONBLOCK);
+#else
+ if ( pipe(fd) < 0 ||
+ fcntl(fd[0], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 ||
+ fcntl(fd[1], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 )
+ ret = -1;
+ else
+ ret = 0;
+#endif
+ if (ret < 0)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
+ return -1;
+ }
+
+ p->writefd = fd[1];
+ p->readfd = fd[0];
+ return 0;
+}
+
+static void
+pipe_close(struct pipepair *p)
+{
+ if (p->readfd >= 0)
+ close(p->readfd);
+ if (p->writefd >= 0)
+ close(p->writefd);
+
+ p->writefd = -1;
+ p->readfd = -1;
+}
+
+static void
+wanted_free(struct streaming_wanted *w)
+{
+ if (!w)
+ return;
+
+ for (int i = 0; i < WANTED_PIPES_MAX; i++)
+ pipe_close(&w->pipes[i]);
+
+ transcode_encode_cleanup(&w->xcode_ctx);
+ evbuffer_free(w->encoded_data);
+ free(w);
+}
+
+static struct streaming_wanted *
+wanted_new(enum streaming_format format, struct media_quality quality)
+{
+ struct streaming_wanted *w;
+
+ CHECK_NULL(L_STREAMING, w = calloc(1, sizeof(struct streaming_wanted)));
+ CHECK_NULL(L_STREAMING, w->encoded_data = evbuffer_new());
+
+ w->quality_out = quality;
+ w->format = format;
+
+ for (int i = 0; i < WANTED_PIPES_MAX; i++)
+ {
+ w->pipes[i].writefd = -1;
+ w->pipes[i].readfd = -1;
+ }
+
+ return w;
+}
+
+static void
+wanted_remove(struct streaming_wanted **wanted, struct streaming_wanted *remove)
+{
+ struct streaming_wanted *prev = NULL;
+ struct streaming_wanted *w;
+
+ for (w = *wanted; w; w = w->next)
+ {
+ if (w == remove)
+ break;
+
+ prev = w;
+ }
+
+ if (!w)
+ return;
+
+ if (!prev)
+ *wanted = remove->next;
+ else
+ prev->next = remove->next;
+
+ wanted_free(remove);
+}
+
+static struct streaming_wanted *
+wanted_add(struct streaming_wanted **wanted, enum streaming_format format, struct media_quality quality)
+{
+ struct streaming_wanted *w;
+
+ w = wanted_new(format, quality);
+ w->next = *wanted;
+ *wanted = w;
+
+ return w;
+}
+
+static struct streaming_wanted *
+wanted_find_byformat(struct streaming_wanted *wanted, enum streaming_format format, struct media_quality quality)
+{
+ struct streaming_wanted *w;
+
+ for (w = wanted; w; w = w->next)
+ {
+ if (w->format == format && quality_is_equal(&w->quality_out, &quality))
+ return w;
+ }
+
+ return NULL;
+}
+
+static struct streaming_wanted *
+wanted_find_byreadfd(struct streaming_wanted *wanted, int readfd)
+{
+ struct streaming_wanted *w;
+ int i;
+
+ for (w = wanted; w; w = w->next)
+ for (i = 0; i < WANTED_PIPES_MAX; i++)
+ {
+ if (w->pipes[i].readfd == readfd)
+ return w;
+ }
+
+ return NULL;
+}
+
+static int
+wanted_session_add(struct pipepair *p, struct streaming_wanted *w)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < WANTED_PIPES_MAX; i++)
+ {
+ if (w->pipes[i].writefd != -1) // In use
+ continue;
+
+ ret = pipe_open(&w->pipes[i]);
+ if (ret < 0)
+ return -1;
+
+ memcpy(p, &w->pipes[i], sizeof(struct pipepair));
+ break;
+ }
+
+ if (i == WANTED_PIPES_MAX)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Cannot add streaming session, max pipe limit reached\n");
+ return -1;
+ }
+
+ w->refcount++;
+ DPRINTF(E_DBG, L_STREAMING, "Session register readfd %d, wanted->refcount=%d\n", p->readfd, w->refcount);
+ return 0;
+}
+
+
+static void
+wanted_session_remove(struct streaming_wanted *w, int readfd)
+{
+ int i;
+
+ for (i = 0; i < WANTED_PIPES_MAX; i++)
+ {
+ if (w->pipes[i].readfd != readfd)
+ continue;
+
+ pipe_close(&w->pipes[i]);
+ break;
+ }
+
+ if (i == WANTED_PIPES_MAX)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Cannot remove streaming session, readfd %d not found\n", readfd);
+ return;
+ }
+
+ w->refcount--;
+ DPRINTF(E_DBG, L_STREAMING, "Session deregister readfd %d, wanted->refcount=%d\n", readfd, w->refcount);
+}
+
+
+/* ----------------------------- Thread: Worker ----------------------------- */
+
+static int
+encode_reset(struct streaming_wanted *w, struct media_quality quality_in)
+{
+ struct media_quality quality_out = w->quality_out;
+ struct decode_ctx *decode_ctx = NULL;
+
+ transcode_encode_cleanup(&w->xcode_ctx);
+
+ if (quality_in.bits_per_sample == 16)
+ decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, &quality_in);
+ else if (quality_in.bits_per_sample == 24)
+ decode_ctx = transcode_decode_setup_raw(XCODE_PCM24, &quality_in);
+ else if (quality_in.bits_per_sample == 32)
+ decode_ctx = transcode_decode_setup_raw(XCODE_PCM32, &quality_in);
+
+ if (!decode_ctx)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Error setting up decoder for input quality sr %d, bps %d, ch %d, cannot MP3 encode\n",
+ quality_in.sample_rate, quality_in.bits_per_sample, quality_in.channels);
+ goto error;
+ }
+
+ w->quality_in = quality_in;
+ w->xcode_ctx = transcode_encode_setup(XCODE_MP3, &quality_out, decode_ctx, NULL, 0, 0);
+ if (!w->xcode_ctx)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Error setting up encoder for output quality sr %d, bps %d, ch %d, cannot MP3 encode\n",
+ quality_out.sample_rate, quality_out.bits_per_sample, quality_out.channels);
+ goto error;
+ }
+
+ transcode_decode_cleanup(&decode_ctx);
+ return 0;
+
+ error:
+ transcode_decode_cleanup(&decode_ctx);
+ return -1;
+}
+
+static int
+encode_frame(struct streaming_wanted *w, struct media_quality quality_in, transcode_frame *frame)
+{
+ int ret;
+
+ if (!w->xcode_ctx || !quality_is_equal(&quality_in, &w->quality_in))
+ {
+ DPRINTF(E_DBG, L_STREAMING, "Resetting transcode context\n");
+ if (encode_reset(w, quality_in) < 0)
+ return -1;
+ }
+
+ ret = transcode_encode(w->encoded_data, w->xcode_ctx, frame, 0);
+ if (ret < 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+encode_write(uint8_t *buf, size_t buflen, struct streaming_wanted *w, struct pipepair *p)
+{
+ int ret;
+
+ if (p->writefd < 0)
+ return;
+
+ ret = write(p->writefd, buf, buflen);
+ if (ret < 0)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Error writing to stream pipe %d (format %d): %s\n", p->writefd, w->format, strerror(errno));
+ wanted_session_remove(w, p->readfd);
+ }
+}
+
+static void
+encode_data_cb(void *arg)
+{
+ struct encode_cmdarg *ctx = arg;
+ transcode_frame *frame;
+ struct streaming_wanted *w;
+ struct streaming_wanted *next;
+ uint8_t *buf;
+ size_t len;
+ int ret;
+ int i;
+
+ frame = transcode_frame_new(ctx->buf, ctx->bufsize, ctx->samples, &ctx->quality);
+ if (!frame)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
+ goto out;
+ }
+
+ pthread_mutex_lock(&streaming_wanted_lck);
+
+ // To make sure we process the frames in order
+ while (ctx->seqnum != streaming.seqnum_encode_next)
+ pthread_cond_wait(&streaming_sequence_cond, &streaming_wanted_lck);
+
+ for (w = streaming.wanted; w; w = next)
+ {
+ next = w->next;
+
+ ret = encode_frame(w, ctx->quality, frame);
+ if (ret < 0)
+ wanted_remove(&streaming.wanted, w); // This will close all the fds, so readers get an error
+
+ len = evbuffer_get_length(w->encoded_data);
+ if (len == 0)
+ continue;
+
+ buf = evbuffer_pullup(w->encoded_data, -1);
+
+ for (i = 0; i < WANTED_PIPES_MAX; i++)
+ encode_write(buf, len, w, &w->pipes[i]);
+
+ evbuffer_drain(w->encoded_data, -1);
+
+ if (w->refcount == 0)
+ wanted_remove(&streaming.wanted, w);
+ }
+
+ streaming.seqnum_encode_next++;
+ pthread_cond_broadcast(&streaming_sequence_cond);
+ pthread_mutex_unlock(&streaming_wanted_lck);
+
+ out:
+ transcode_frame_free(frame);
+ free(ctx->buf);
+}
+
+static void *
+streaming_metadata_prepare(struct output_metadata *metadata)
+{
+ struct db_queue_item *queue_item;
+ char *title;
+
+ queue_item = db_queue_fetch_byitemid(metadata->item_id);
+ if (!queue_item)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Could not fetch queue item id %d for new metadata\n", metadata->item_id);
+ return NULL;
+ }
+
+ title = safe_asprintf("%s - %s", queue_item->title, queue_item->artist);
+ free_queue_item(queue_item, 0);
+
+ return title;
+}
+
+
+/* ----------------------------- Thread: httpd ------------------------------ */
+
+int
+streaming_session_register(enum streaming_format format, struct media_quality quality)
+{
+ struct streaming_wanted *w;
+ struct pipepair pipe;
+ int ret;
+
+ pthread_mutex_lock(&streaming_wanted_lck);
+ w = wanted_find_byformat(streaming.wanted, format, quality);
+ if (!w)
+ w = wanted_add(&streaming.wanted, format, quality);
+
+ ret = wanted_session_add(&pipe, w);
+ if (ret < 0)
+ pipe.readfd = -1;
+
+ pthread_mutex_unlock(&streaming_wanted_lck);
+
+ return pipe.readfd;
+}
+
+void
+streaming_session_deregister(int readfd)
+{
+ struct streaming_wanted *w;
+
+ pthread_mutex_lock(&streaming_wanted_lck);
+ w = wanted_find_byreadfd(streaming.wanted, readfd);
+ if (!w)
+ goto out;
+
+ wanted_session_remove(w, readfd);
+
+ if (w->refcount == 0)
+ wanted_remove(&streaming.wanted, w);
+
+ out:
+ pthread_mutex_unlock(&streaming_wanted_lck);
+}
+
+// Not thread safe, but only called once during httpd init
+void
+streaming_metadatacb_register(streaming_metadatacb cb)
+{
+ streaming.metadatacb = cb;
+}
+
+/* ----------------------------- Thread: Player ----------------------------- */
+
+static void
+encode_worker_invoke(uint8_t *buf, size_t bufsize, int samples, struct media_quality quality)
+{
+ struct encode_cmdarg ctx;
+
+ if (quality.channels == 0)
+ {
+ DPRINTF(E_LOG, L_STREAMING, "Streaming quality is zero (%d/%d/%d)\n",
+ quality.sample_rate, quality.bits_per_sample, quality.channels);
+ return;
+ }
+
+ CHECK_NULL(L_STREAMING, ctx.buf = malloc(bufsize));
+ memcpy(ctx.buf, buf, bufsize);
+ ctx.bufsize = bufsize;
+ ctx.samples = samples;
+ ctx.quality = quality;
+ ctx.seqnum = streaming.seqnum;
+
+ streaming.seqnum++;
+
+ worker_execute(encode_data_cb, &ctx, sizeof(struct encode_cmdarg), 0);
+}
+
+static void
+silenceev_cb(evutil_socket_t fd, short event, void *arg)
+{
+ uint8_t silence[SILENCE_BUF_SIZE] = { 0 };
+ int samples;
+
+ // No lock since this is just an early exit, it doesn't need to be accurate
+ if (!streaming.wanted)
+ return;
+
+ samples = BTOS(SILENCE_BUF_SIZE, streaming.last_quality.bits_per_sample, streaming.last_quality.channels);
+
+ encode_worker_invoke(silence, SILENCE_BUF_SIZE, samples, streaming.last_quality);
+
+ evtimer_add(streaming.silenceev, &streaming.silencetv);
+}
+
+static void
+streaming_write(struct output_buffer *obuf)
+{
+ // No lock since this is just an early exit, it doesn't need to be accurate
+ if (!streaming.wanted)
+ return;
+
+ encode_worker_invoke(obuf->data[0].buffer, obuf->data[0].bufsize, obuf->data[0].samples, obuf->data[0].quality);
+
+ streaming.last_quality = obuf->data[0].quality;
+
+ // In case this is the last player write() we want to start streaming silence
+ evtimer_add(streaming.silenceev, &streaming.silencetv);
+}
+
+static void
+streaming_metadata_send(struct output_metadata *metadata)
+{
+ char *title = metadata->priv;
+
+ // Calls back to httpd_streaming to update the title
+ if (streaming.metadatacb)
+ streaming.metadatacb(title);
+
+ free(title);
+ outputs_metadata_free(metadata);
+}
+
+static int
+streaming_init(void)
+{
+ CHECK_NULL(L_STREAMING, streaming.silenceev = event_new(evbase_player, -1, 0, silenceev_cb, NULL));
+ CHECK_ERR(L_STREAMING, mutex_init(&streaming_wanted_lck));
+ CHECK_ERR(L_STREAMING, pthread_cond_init(&streaming_sequence_cond, NULL));
+
+ return 0;
+}
+
+static void
+streaming_deinit(void)
+{
+ event_free(streaming.silenceev);
+}
+
struct output_definition output_streaming =
{
@@ -30,5 +610,9 @@ struct output_definition output_streaming =
.type = OUTPUT_TYPE_STREAMING,
.priority = 0,
.disabled = 0,
+ .init = streaming_init,
+ .deinit = streaming_deinit,
.write = streaming_write,
+ .metadata_prepare = streaming_metadata_prepare,
+ .metadata_send = streaming_metadata_send,
};
diff --git a/src/outputs/streaming.h b/src/outputs/streaming.h
new file mode 100644
index 00000000..34673345
--- /dev/null
+++ b/src/outputs/streaming.h
@@ -0,0 +1,23 @@
+
+#ifndef __STREAMING_H__
+#define __STREAMING_H__
+
+#include "misc.h" // struct media_quality
+
+typedef void (*streaming_metadatacb)(char *metadata);
+
+enum streaming_format
+{
+ STREAMING_FORMAT_MP3,
+};
+
+int
+streaming_session_register(enum streaming_format format, struct media_quality quality);
+
+void
+streaming_session_deregister(int readfd);
+
+void
+streaming_metadatacb_register(streaming_metadatacb cb);
+
+#endif /* !__STREAMING_H__ */
diff --git a/src/worker.c b/src/worker.c
index 23ad95c4..ecbc9836 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -29,6 +29,10 @@
#include
#include
#include
+#include
+
+#include
+
#include
#include
@@ -36,9 +40,17 @@
#include "db.h"
#include "logger.h"
#include "worker.h"
-#include "commands.h"
+#include "evthr.h"
#include "misc.h"
+#define THREADPOOL_NTHREADS 4
+
+static struct evthr_pool *worker_threadpool;
+static __thread struct evthr *worker_thr;
+
+
+/* ----------------------------- CALLBACK EXECUTION ------------------------- */
+/* Worker threads */
struct worker_arg
{
@@ -49,19 +61,6 @@ struct worker_arg
};
-/* --- Globals --- */
-// worker thread
-static pthread_t tid_worker;
-
-// Event base, pipes and events
-struct event_base *evbase_worker;
-static int g_initialized;
-static struct commands_base *cmdbase;
-
-
-/* ---------------------------- CALLBACK EXECUTION ------------------------- */
-/* Thread: worker */
-
static void
execute_cb(int fd, short what, void *arg)
{
@@ -74,64 +73,47 @@ execute_cb(int fd, short what, void *arg)
free(cmdarg);
}
-
-static enum command_state
-execute(void *arg, int *retval)
+static void
+execute(struct evthr *thr, void *arg, void *shared)
{
struct worker_arg *cmdarg = arg;
struct timeval tv = { cmdarg->delay, 0 };
+ struct event_base *evbase;
if (cmdarg->delay)
{
- cmdarg->timer = evtimer_new(evbase_worker, execute_cb, cmdarg);
+ evbase = evthr_get_base(thr);
+ cmdarg->timer = evtimer_new(evbase, execute_cb, cmdarg);
evtimer_add(cmdarg->timer, &tv);
-
- *retval = 0;
- return COMMAND_PENDING; // Not done yet, ask caller not to free cmd
+ return;
}
cmdarg->cb(cmdarg->cb_arg);
free(cmdarg->cb_arg);
-
- *retval = 0;
- return COMMAND_END;
+ free(cmdarg);
}
-
-/* --------------------------------- MAIN --------------------------------- */
-/* Thread: worker */
-
-static void *
-worker(void *arg)
+static void
+init_cb(struct evthr *thr, void *shared)
{
- int ret;
+ CHECK_ERR(L_MAIN, db_perthread_init());
- ret = db_perthread_init();
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MAIN, "Error: DB init failed (worker thread)\n");
- pthread_exit(NULL);
- }
+ worker_thr = thr;
- g_initialized = 1;
+ thread_setname(pthread_self(), "worker");
+}
- event_base_dispatch(evbase_worker);
-
- if (g_initialized)
- {
- DPRINTF(E_LOG, L_MAIN, "Worker event loop terminated ahead of time!\n");
- g_initialized = 0;
- }
+static void
+exit_cb(struct evthr *thr, void *shared)
+{
+ worker_thr = NULL;
db_perthread_deinit();
-
- pthread_exit(NULL);
}
/* ---------------------------- Our worker API --------------------------- */
-/* Thread: player */
void
worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
{
@@ -164,7 +146,13 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
cmdarg->cb_arg = argcpy;
cmdarg->delay = delay;
- commands_exec_async(cmdbase, execute, cmdarg);
+ evthr_pool_defer(worker_threadpool, execute, cmdarg);
+}
+
+struct event_base *
+worker_evbase_get(void)
+{
+ return evthr_get_base(worker_thr);
}
int
@@ -172,51 +160,30 @@ worker_init(void)
{
int ret;
- evbase_worker = event_base_new();
- if (!evbase_worker)
+ worker_threadpool = evthr_pool_wexit_new(THREADPOOL_NTHREADS, init_cb, exit_cb, NULL);
+ if (!worker_threadpool)
{
- DPRINTF(E_LOG, L_MAIN, "Could not create an event base\n");
- goto evbase_fail;
+ DPRINTF(E_LOG, L_MAIN, "Could not create worker thread pool\n");
+ goto error;
}
- cmdbase = commands_base_new(evbase_worker, NULL);
-
- ret = pthread_create(&tid_worker, NULL, worker, NULL);
+ ret = evthr_pool_start(worker_threadpool);
if (ret < 0)
{
- DPRINTF(E_LOG, L_MAIN, "Could not spawn worker thread: %s\n", strerror(errno));
-
- goto thread_fail;
+ DPRINTF(E_LOG, L_MAIN, "Could not spawn worker threads\n");
+ goto error;
}
- thread_setname(tid_worker, "worker");
-
return 0;
- thread_fail:
- commands_base_free(cmdbase);
- event_base_free(evbase_worker);
- evbase_worker = NULL;
-
- evbase_fail:
+ error:
+ worker_deinit();
return -1;
}
void
worker_deinit(void)
{
- int ret;
-
- g_initialized = 0;
- commands_base_destroy(cmdbase);
-
- ret = pthread_join(tid_worker, NULL);
- if (ret != 0)
- {
- DPRINTF(E_FATAL, L_MAIN, "Could not join worker thread: %s\n", strerror(errno));
- return;
- }
-
- // Free event base (should free events too)
- event_base_free(evbase_worker);
+ evthr_pool_stop(worker_threadpool);
+ evthr_pool_free(worker_threadpool);
}
diff --git a/src/worker.h b/src/worker.h
index 125e9730..32afea7c 100644
--- a/src/worker.h
+++ b/src/worker.h
@@ -2,8 +2,10 @@
#ifndef __WORKER_H__
#define __WORKER_H__
+#include
+
/* The worker thread is made for running asyncronous tasks from a real time
- * thread, mainly the player thread.
+ * thread.
* The worker_execute() function will trigger a callback from the worker thread.
* Before returning the function will copy the argument given, so the caller
@@ -19,6 +21,11 @@
void
worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay);
+/* Can be called within a callback to get the worker thread's event base
+ */
+struct event_base *
+worker_evbase_get(void);
+
int
worker_init(void);