From 5f342ea60b61cd76294bfd0252b5e0470ce8beec Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 18 Dec 2022 10:24:43 +0100 Subject: [PATCH 01/21] [httpd/artwork] Drop support for libevent <2.1.4 --- configure.ac | 8 +------- docs/building.md | 2 +- src/artwork.c | 14 -------------- src/evrtsp/rtsp.c | 11 ----------- src/httpd.c | 42 ------------------------------------------ 5 files changed, 2 insertions(+), 75 deletions(-) diff --git a/configure.ac b/configure.ac index 595fbc27..16822734 100644 --- a/configure.ac +++ b/configure.ac @@ -148,13 +148,7 @@ 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]) dnl Check for evhttp_connection_get_peer() signature AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ 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/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/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/httpd.c b/src/httpd.c index 397ccb72..1f66b099 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -42,10 +42,6 @@ #include #include #include -#ifdef HAVE_LIBEVENT2_OLD -# include -# include -#endif #include #include "logger.h" @@ -134,10 +130,6 @@ static pthread_t tid_httpd; static const char *allow_origin; static int httpd_port; -#ifdef HAVE_LIBEVENT2_OLD -struct stream_ctx *g_st; -#endif - /* -------------------------------- HELPERS --------------------------------- */ @@ -550,11 +542,6 @@ stream_end(struct stream_ctx *st, int failed) close(st->fd); } -#ifdef HAVE_LIBEVENT2_OLD - if (g_st == st) - g_st = NULL; -#endif - free(st); } @@ -592,15 +579,6 @@ stream_chunk_resched_cb(struct evhttp_connection *evcon, void *arg) } } -#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); -} -#endif - static void stream_chunk_xcode_cb(int fd, short event, void *arg) { @@ -648,17 +626,7 @@ stream_chunk_xcode_cb(int fd, short event, void *arg) 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 st->offset += ret; @@ -714,17 +682,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) 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 st->offset += ret; From 23979d223c33d4e697c4bb69eb35b16cd22b7b80 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 18 Dec 2022 13:08:29 +0100 Subject: [PATCH 02/21] [httpd] Refactor http handlers so they are initialized centrally Means regex.h doesn't need to be in httpd.h making it easier to e.g. experiment with evhtp. --- src/httpd.c | 65 ++++++++++++++++++++++++----- src/httpd.h | 9 +++- src/httpd_artworkapi.c | 21 +--------- src/httpd_daap.c | 37 ++--------------- src/httpd_dacp.c | 94 +++++++++++------------------------------- src/httpd_jsonapi.c | 22 ++-------- src/httpd_oauth.c | 21 +--------- src/httpd_rsp.c | 21 +--------- 8 files changed, 98 insertions(+), 192 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index 1f66b099..76d73b99 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -42,6 +42,8 @@ #include #include #include + +#include #include #include "logger.h" @@ -945,8 +947,8 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par struct httpd_request *hreq; struct evhttp_connection *evcon; struct evkeyvalq *headers; + struct httpd_uri_map *uri; int req_method; - int i; int ret; CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request))); @@ -975,26 +977,69 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par hreq->user_agent = user_agent; // Find a handler for the path - for (i = 0; uri_map[i].handler; i++) + for (uri = uri_map; uri->handler; uri++) { // Check if handler supports the current http request method - if (uri_map[i].method && req_method && !(req_method & uri_map[i].method)) + if (uri->method && req_method && !(req_method & uri->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 - } + ret = regexec(uri->preg, uri_parsed->path, 0, NULL, 0); + if (ret != 0) + continue; + + hreq->handler = uri->handler; + return hreq; // Success } // Handler not found, that's an error free(hreq); - return NULL; } +int +httpd_handlers_set(struct httpd_uri_map *uri_map) +{ + struct httpd_uri_map *uri; + char buf[64]; + int ret; + + for (uri = uri_map; uri->handler; uri++) + { + uri->preg = calloc(1, sizeof(regex_t)); + if (!uri->preg) + { + DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, out of memory"); + goto error; + } + + 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; + } + } + + return 0; + + error: + httpd_handlers_unset(uri_map); + return -1; +} + +void +httpd_handlers_unset(struct httpd_uri_map *uri_map) +{ + struct httpd_uri_map *uri; + + for (uri = uri_map; uri->preg; uri++) + { + regfree(uri->preg); // Frees allocation by regcomp + free(uri->preg); // Frees our own calloc + } +} + /* Thread: httpd */ void httpd_stream_file(struct evhttp_request *req, int id) diff --git a/src/httpd.h b/src/httpd.h index 1dd6e825..053d87b9 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -3,7 +3,6 @@ #define __HTTPD_H__ #include -#include #include #include #include @@ -72,7 +71,7 @@ struct httpd_uri_map int method; char *regexp; int (*handler)(struct httpd_request *hreq); - regex_t preg; + void *preg; }; /* @@ -97,6 +96,12 @@ httpd_uri_parse(const char *uri); 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); +int +httpd_handlers_set(struct httpd_uri_map *uri_map); + +void +httpd_handlers_unset(struct httpd_uri_map *uri_map); + void httpd_stream_file(struct evhttp_request *req, int id); diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index 4414d90b..f52de8c0 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -211,21 +211,7 @@ artworkapi_is_request(const char *path) 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; - } - } + CHECK_ERR(L_WEB, httpd_handlers_set(artworkapi_handlers)); return 0; } @@ -233,8 +219,5 @@ artworkapi_init(void) void artworkapi_deinit(void) { - int i; - - for (i = 0; artworkapi_handlers[i].handler; i++) - regfree(&artworkapi_handlers[i].preg); + httpd_handlers_unset(artworkapi_handlers); } diff --git a/src/httpd_daap.c b/src/httpd_daap.c index e87d1f45..49b16e89 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -42,7 +42,6 @@ #include #include -#include #include "httpd_daap.h" #include "logger.h" @@ -993,7 +992,6 @@ 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; @@ -1073,18 +1071,7 @@ daap_reply_update(struct httpd_request *hreq) */ 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); - } + evhttp_connection_set_closecb(evcon, update_fail_cb, ur); return DAAP_REPLY_NONE; } @@ -2424,25 +2411,11 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) 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; - } - } + CHECK_ERR(L_DAAP, httpd_handlers_set(daap_handlers)); return 0; } @@ -2453,10 +2426,6 @@ 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) { @@ -2477,4 +2446,6 @@ daap_deinit(void) update_free(ur); } + + httpd_handlers_unset(daap_handlers); } diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index bee56149..f53124f9 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -36,7 +36,6 @@ #endif #include -#include #include "httpd_dacp.h" #include "httpd_daap.h" @@ -2236,7 +2235,6 @@ 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; @@ -2297,18 +2295,7 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) */ 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); - } + evhttp_connection_set_closecb(evcon, update_fail_cb, ur); return 0; } @@ -2895,12 +2882,7 @@ dacp_is_request(const char *path) 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; @@ -2914,72 +2896,42 @@ dacp_init(void) dummy_queue_item.album = CFG_NAME_UNKNOWN_ALBUM; dummy_queue_item.genre = CFG_NAME_UNKNOWN_GENRE; + CHECK_ERR(L_DACP, httpd_handlers_set(dacp_handlers)); + #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; + goto error; } + + CHECK_NULL(L_DACP, updateev = event_new(evbase_httpd, update_efd, EV_READ, playstatusupdate_cb, NULL)); #else # ifdef HAVE_PIPE2 - ret = pipe2(update_pipe, O_CLOEXEC); + int ret = pipe2(update_pipe, O_CLOEXEC); # else - ret = pipe(update_pipe); + int ret = pipe(update_pipe); # endif if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not create update pipe: %s\n", strerror(errno)); - - return -1; + goto error; } + + CHECK_NULL(L_DACP, updateev = event_new(evbase_httpd, update_pipe[0], EV_READ, playstatusupdate_cb, NULL)); #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_NULL(L_DACP, seek_timer = evtimer_new(evbase_httpd, seek_timer_cb, NULL)); 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 + error: + dacp_deinit(); return -1; } @@ -2988,14 +2940,6 @@ 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) { @@ -3011,7 +2955,13 @@ dacp_deinit(void) free(ur); } - event_free(updateev); + listener_remove(dacp_playstatus_update_handler); + + if (seek_timer) + event_free(seek_timer); + + if (updateev) + event_free(updateev); #ifdef HAVE_EVENTFD close(update_efd); @@ -3019,4 +2969,6 @@ dacp_deinit(void) close(update_pipe[0]); close(update_pipe[1]); #endif + + httpd_handlers_unset(dacp_handlers); } diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 56cdb771..966fe257 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4794,22 +4794,9 @@ jsonapi_is_request(const char *path) 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; - } - } + CHECK_ERR(L_WEB, httpd_handlers_set(adm_handlers)); default_playlist_directory = NULL; allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists"); @@ -4840,10 +4827,7 @@ jsonapi_init(void) void jsonapi_deinit(void) { - int i; - - for (i = 0; adm_handlers[i].handler; i++) - regfree(&adm_handlers[i].preg); - free(default_playlist_directory); + + httpd_handlers_unset(adm_handlers); } diff --git a/src/httpd_oauth.c b/src/httpd_oauth.c index 202b2633..29bd5cda 100644 --- a/src/httpd_oauth.c +++ b/src/httpd_oauth.c @@ -125,21 +125,7 @@ oauth_is_request(const char *path) 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; - } - } + CHECK_ERR(L_WEB, httpd_handlers_set(oauth_handlers)); return 0; } @@ -147,8 +133,5 @@ oauth_init(void) void oauth_deinit(void) { - int i; - - for (i = 0; oauth_handlers[i].handler; i++) - regfree(&oauth_handlers[i].preg); + httpd_handlers_unset(oauth_handlers); } diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index 16c4b730..da67a781 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -905,23 +905,9 @@ rsp_is_request(const char *path) 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; - } - } + CHECK_ERR(L_RSP, httpd_handlers_set(rsp_handlers)); return 0; } @@ -929,8 +915,5 @@ rsp_init(void) void rsp_deinit(void) { - int i; - - for (i = 0; rsp_handlers[i].handler; i++) - regfree(&rsp_handlers[i].preg); + httpd_handlers_unset(rsp_handlers); } From 631996f1337ef71b50260071e57d18b676943db4 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Dec 2022 23:09:22 +0100 Subject: [PATCH 03/21] [mpd] Decouple mpd and httpd mpd is based on evhttp, so if httpd should use another backend they need to be decoupled. No need to use httpd_send_reply/error, since CORS headers are not relevant to MPD artwork serving. --- src/mpd.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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); From 316d932d99031130b0f3e0681d8fef08fed93cab Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Dec 2022 23:26:50 +0100 Subject: [PATCH 04/21] [dmap] Move dmap_send_error() to dacp_send_error() Means dmap_common doesn't need a dependency to httpd and http replying is kept within httpd_xxx modules. --- src/dmap_common.c | 26 ---------------- src/dmap_common.h | 4 --- src/httpd_dacp.c | 76 +++++++++++++++++++++++++++++++---------------- 3 files changed, 50 insertions(+), 56 deletions(-) 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/httpd_dacp.c b/src/httpd_dacp.c index f53124f9..aa8f3c2d 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -154,6 +154,30 @@ static struct db_queue_item dummy_queue_item; /* -------------------------------- HELPERS --------------------------------- */ +static void +dacp_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_DACP, "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); +} + static void dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { @@ -1240,7 +1264,7 @@ dacp_reply_cue_play(struct httpd_request *hreq) { 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->req, "cacr", "Could not build song queue"); return -1; } } @@ -1280,7 +1304,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->req, "cacr", "Playback failed to start"); return -1; } } @@ -1288,7 +1312,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->req, "cacr", "Playback failed to start"); return -1; } } @@ -1303,7 +1327,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->req, "cacr", "Playback failed to start"); return -1; } } @@ -1315,7 +1339,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->req, "cacr", "Playback failed to start"); return -1; } } @@ -1326,7 +1350,7 @@ 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->req, "cacr", "Playback failed to start"); return -1; } @@ -1378,7 +1402,7 @@ dacp_reply_cue(struct httpd_request *hreq) { 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->req, "cacr", "No command in cue request"); return -1; } @@ -1390,7 +1414,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->req, "cacr", "Unknown command in cue request"); return -1; } } @@ -1892,7 +1916,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->req, "ceQR", "Database error"); return -1; } @@ -1962,7 +1986,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->req, "cacr", "Invalid request"); return -1; } } @@ -1981,7 +2005,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) { 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->req, "cacr", "Invalid request"); return -1; } @@ -2010,7 +2034,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->req, "cacr", "Invalid request"); return -1; } @@ -2022,7 +2046,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->req, "cacr", "Invalid request"); return -1; } @@ -2055,7 +2079,7 @@ 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->req, "cacr", "Playback failed to start"); return -1; } @@ -2090,7 +2114,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->req, "cacr", "Invalid request"); return -1; } @@ -2099,7 +2123,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->req, "cacr", "Invalid request"); return -1; } @@ -2134,7 +2158,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->req, "cacr", "Invalid request"); return -1; } @@ -2207,7 +2231,7 @@ dacp_reply_playqueueedit(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - dmap_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq->req, "cmst", "Invalid request"); return -1; } @@ -2225,7 +2249,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->req, "cmst", "Invalid request"); return -1; } } @@ -2248,7 +2272,7 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Missing revision-number in update request\n"); - dmap_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq->req, "cmst", "Invalid request"); return -1; } @@ -2257,7 +2281,7 @@ 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->req, "cmst", "Invalid request"); return -1; } @@ -2281,7 +2305,7 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) { 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->req, "cmst", "Out of memory"); return -1; } @@ -2410,7 +2434,7 @@ dacp_reply_getproperty(struct httpd_request *hreq) { DPRINTF(E_WARN, L_DACP, "Invalid DACP getproperty request, no properties\n"); - dmap_send_error(hreq->req, "cmgt", "Invalid request"); + dacp_send_error(hreq->req, "cmgt", "Invalid request"); return -1; } @@ -2419,7 +2443,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->req, "cmgt", "Out of memory"); return -1; } @@ -2428,7 +2452,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->req, "cmgt", "Out of memory"); goto out_free_propstr; } @@ -2441,7 +2465,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->req, "cmgt", "Server error"); goto out_free_proplist; } } From 2e31a3d4f3c8a773e538c407c47e8153cf845d4b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 22 Dec 2022 15:02:17 +0100 Subject: [PATCH 05/21] [misc] 'unsigned short' instead of 'short unsigned' as arg to net_evhttp_bind() --- src/misc.c | 2 +- src/misc.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc.c b/src/misc.c index 2a145988..3acd732b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -375,7 +375,7 @@ 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_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..60455415 100644 --- a/src/misc.h +++ b/src/misc.h @@ -55,7 +55,7 @@ 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_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name); // Just checks if the protocol is http or https bool From 83b8a4eb3f197daa1e92905002f1bcf8bc6805b6 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Dec 2022 23:53:37 +0100 Subject: [PATCH 06/21] [http] Use curl for URL parsing instead of depending on httpd Makes it easier to make the httpd parsing internal. --- src/http.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) 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) { From 2778088c52b751cddda55b2987b8dccf7c65849c Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Dec 2022 22:59:36 +0100 Subject: [PATCH 07/21] [httpd] Refactor to use modules for daap, dacp, json api etc. Removes a lot of code duplication which hopefully will make it easier to add support for evhtp as http engine. --- src/Makefile.am | 12 +- src/httpd.c | 382 ++++++++++++++++------------------------- src/httpd.h | 15 +- src/httpd_artworkapi.c | 60 +++---- src/httpd_artworkapi.h | 18 -- src/httpd_daap.c | 104 +++++------ src/httpd_daap.h | 14 -- src/httpd_dacp.c | 52 +++--- src/httpd_dacp.h | 19 -- src/httpd_internal.h | 37 ++++ src/httpd_jsonapi.c | 76 ++++---- src/httpd_jsonapi.h | 19 -- src/httpd_oauth.c | 50 ++---- src/httpd_oauth.h | 18 -- src/httpd_rsp.c | 46 ++--- src/httpd_rsp.h | 19 -- src/httpd_streaming.c | 73 +++++--- src/httpd_streaming.h | 13 -- 18 files changed, 394 insertions(+), 633 deletions(-) delete mode 100644 src/httpd_artworkapi.h delete mode 100644 src/httpd_dacp.h create mode 100644 src/httpd_internal.h delete mode 100644 src/httpd_jsonapi.h delete mode 100644 src/httpd_oauth.h delete mode 100644 src/httpd_rsp.h diff --git a/src/Makefile.am b/src/Makefile.am index fa01a0a9..00c0f83a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -92,14 +92,14 @@ 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.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_dacp.c \ + httpd_jsonapi.c \ httpd_streaming.c httpd_streaming.h \ - httpd_oauth.c httpd_oauth.h \ - httpd_artworkapi.c httpd_artworkapi.h \ + httpd_oauth.c \ + httpd_artworkapi.c \ http.c http.h \ dmap_common.c dmap_common.h \ transcode.c transcode.h \ diff --git a/src/httpd.c b/src/httpd.c index 76d73b99..5d0f3a4f 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -52,13 +52,7 @@ #include "misc.h" #include "worker.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" @@ -67,7 +61,6 @@ # include "websocket.h" #endif - #define STREAM_CHUNK_SIZE (64 * 1024) #define ERR_PAGE "\n\n" \ "%d %s\n" \ @@ -79,6 +72,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; @@ -114,8 +127,6 @@ 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; @@ -161,98 +172,125 @@ 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 ------------------------------ */ - void httpd_redirect_to(struct evhttp_request *req, const char *path) { @@ -745,7 +783,9 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) { struct evkeyvalq *input_headers; struct evkeyvalq *output_headers; + struct httpd_request hreq; struct httpd_uri_parsed *parsed; + struct httpd_module *m; const char *uri; // Clear the proxy request flag set by evhttp if the request URI was absolute. @@ -790,40 +830,11 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) goto serve_file; } - /* Dispatch protocol-specific handlers */ - if (dacp_is_request(parsed->path)) + m = modules_search(parsed->path); + if (m) { - 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); + httpd_request_parse(&hreq, req, parsed, NULL, m->handlers); + m->request(&hreq); goto out; } @@ -941,20 +952,21 @@ httpd_uri_parse(const char *uri) 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) +int +httpd_request_parse(struct httpd_request *hreq, 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; struct httpd_uri_map *uri; int req_method; int ret; - CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request))); + memset(hreq, 0, sizeof(struct httpd_request)); // Note req is allowed to be NULL hreq->req = req; + hreq->uri = uri_parsed->uri; hreq->uri_parsed = uri_parsed; hreq->query = &(uri_parsed->ev_query); req_method = 0; @@ -988,58 +1000,13 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par continue; hreq->handler = uri->handler; - return hreq; // Success + return 0; // Success } // Handler not found, that's an error - free(hreq); - return NULL; -} - -int -httpd_handlers_set(struct httpd_uri_map *uri_map) -{ - struct httpd_uri_map *uri; - char buf[64]; - int ret; - - for (uri = uri_map; uri->handler; uri++) - { - uri->preg = calloc(1, sizeof(regex_t)); - if (!uri->preg) - { - DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, out of memory"); - goto error; - } - - 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; - } - } - - return 0; - - error: - httpd_handlers_unset(uri_map); return -1; } -void -httpd_handlers_unset(struct httpd_uri_map *uri_map) -{ - struct httpd_uri_map *uri; - - for (uri = uri_map; uri->preg; uri++) - { - regfree(uri->preg); // Frees allocation by regcomp - free(uri->preg); // Frees our own calloc - } -} - /* Thread: httpd */ void httpd_stream_file(struct evhttp_request *req, int id) @@ -1631,7 +1598,7 @@ httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passw headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "WWW-Authenticate", header); - evbuffer_add(evbuf, http_reply_401, strlen(http_reply_401)); + evbuffer_add_printf(evbuf, ERR_PAGE, 401, "Unauthorized", "Authorization required"); httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP); @@ -1688,54 +1655,6 @@ httpd_init(const char *webroot) return -1; } - ret = rsp_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; - } - #ifdef HAVE_LIBWEBSOCKETS ret = websocket_init(); if (ret < 0) @@ -1746,7 +1665,13 @@ httpd_init(const char *webroot) } #endif - streaming_init(); + ret = modules_init(); + if (ret < 0) + { + DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n"); + + goto modules_fail; + } #ifdef HAVE_EVENTFD exit_efd = eventfd(0, EFD_CLOEXEC); @@ -1835,23 +1760,12 @@ httpd_init(const char *webroot) close(exit_pipe[1]); #endif pipe_fail: - streaming_deinit(); + modules_fail: + modules_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); return -1; @@ -1891,15 +1805,11 @@ httpd_deinit(void) return; } - streaming_deinit(); + modules_deinit(); + #ifdef HAVE_LIBWEBSOCKETS websocket_deinit(); #endif - oauth_deinit(); - jsonapi_deinit(); - rsp_deinit(); - dacp_deinit(); - daap_deinit(); #ifdef HAVE_EVENTFD close(exit_efd); diff --git a/src/httpd.h b/src/httpd.h index 053d87b9..04d965a9 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -44,6 +44,8 @@ struct httpd_uri_parsed struct httpd_request { // User-agent (if available) const char *user_agent; + // TODO + const char *uri; // The parsed request URI given to us by httpd_uri_parse struct httpd_uri_parsed *uri_parsed; // Shortcut to &uri_parsed->ev_query @@ -87,20 +89,13 @@ 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 + * Parse a request into the httpd_request struct. Nothing is copied, so 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); - int -httpd_handlers_set(struct httpd_uri_map *uri_map); - -void -httpd_handlers_unset(struct httpd_uri_map *uri_map); +httpd_request_parse(struct httpd_request *hreq, 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); diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index f52de8c0..61cca7a9 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -25,7 +25,7 @@ #include #include -#include "httpd_artworkapi.h" +#include "httpd_internal.h" #include "logger.h" #include "misc.h" #include "player.h" @@ -149,23 +149,22 @@ static struct httpd_uri_map artworkapi_handlers[] = /* ------------------------------- 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); + DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", hreq->uri); - if (!httpd_admin_check_auth(req)) + if (!httpd_admin_check_auth(hreq->req)) 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->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -176,48 +175,33 @@ artworkapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_pars 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->req, status_code, "OK", hreq->reply, 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->req, status_code, "No Content", hreq->reply, 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->req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); break; case HTTP_BADREQUEST: /* 400 Bad Request */ - httpd_send_error(req, status_code, "Bad Request"); + httpd_send_error(hreq->req, status_code, "Bad Request"); break; case HTTP_NOTFOUND: /* 404 Not Found */ - httpd_send_error(req, status_code, "Not Found"); + httpd_send_error(hreq->req, 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->req, 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) -{ - CHECK_ERR(L_WEB, httpd_handlers_set(artworkapi_handlers)); - - return 0; -} - -void -artworkapi_deinit(void) -{ - httpd_handlers_unset(artworkapi_handlers); -} + .name = "Artwork API", + .type = MODULE_ARTWORKAPI, + .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 49b16e89..e6663802 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -43,6 +43,7 @@ #include +#include "httpd_internal.h" #include "httpd_daap.h" #include "logger.h" #include "db.h" @@ -2224,10 +2225,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; @@ -2237,14 +2237,13 @@ 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); + DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", hreq->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); + DPRINTF(E_LOG, L_DAAP, "Unrecognized path in DAAP request: '%s'\n", hreq->uri); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -2254,7 +2253,7 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { 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); } @@ -2270,12 +2269,11 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) ret = daap_request_authorize(hreq); if (ret < 0) { - free(hreq); return; } // Set reply headers - headers = evhttp_request_get_output_headers(req); + headers = evhttp_request_get_output_headers(hreq->req); evhttp_add_header(headers, "Accept-Ranges", "bytes"); evhttp_add_header(headers, "DAAP-Server", PACKAGE_NAME "/" VERSION); // Content-Type for all replies, even the actual audio streaming. Note that @@ -2287,15 +2285,14 @@ daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new()); // Try the cache - ret = cache_daap_get(hreq->reply, uri_parsed->uri); + ret = cache_daap_get(hreq->reply, 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 + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply evbuffer_free(hreq->reply); - free(hreq); return; } @@ -2312,38 +2309,9 @@ 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); + cache_daap_add(hreq->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; } int @@ -2363,7 +2331,7 @@ daap_session_is_valid(int id) struct evbuffer * daap_reply_build(const char *uri, const char *user_agent, int is_remote) { - struct httpd_request *hreq; + struct httpd_request hreq; struct httpd_uri_parsed *uri_parsed; struct evbuffer *reply; struct daap_session session; @@ -2377,50 +2345,46 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) if (!uri_parsed) return NULL; - hreq = httpd_request_parse(NULL, uri_parsed, user_agent, daap_handlers); - if (!hreq) + ret = httpd_request_parse(&hreq, NULL, uri_parsed, user_agent, daap_handlers); + if (ret < 0) { 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; + goto out; } memset(&session, 0, sizeof(struct daap_session)); session.is_remote = (bool)is_remote; - hreq->extra_data = &session; + hreq.extra_data = &session; - CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new()); + CHECK_NULL(L_DAAP, hreq.reply = evbuffer_new()); - ret = hreq->handler(hreq); + ret = hreq.handler(&hreq); if (ret < 0) { - evbuffer_free(hreq->reply); - goto out_free_hreq; + evbuffer_free(hreq.reply); + goto out; } - reply = hreq->reply; + reply = hreq.reply; - out_free_hreq: - free(hreq); - out_free_uri: + out: httpd_uri_free(uri_parsed); return reply; } -int +static int daap_init(void) { srand((unsigned)time(NULL)); current_rev = 2; update_requests = NULL; - CHECK_ERR(L_DAAP, httpd_handlers_set(daap_handlers)); - return 0; } -void +static void daap_deinit(void) { struct daap_session *s; @@ -2446,6 +2410,20 @@ daap_deinit(void) update_free(ur); } - - httpd_handlers_unset(daap_handlers); } + +struct httpd_module httpd_daap = +{ + .name = "DAAP", + .type = MODULE_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 aa8f3c2d..09d97c30 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -37,7 +37,7 @@ #include -#include "httpd_dacp.h" +#include "httpd_internal.h" #include "httpd_daap.h" #include "logger.h" #include "misc.h" @@ -2862,24 +2862,22 @@ 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); + DPRINTF(E_DBG, L_DACP, "DACP request: '%s'\n", hreq->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->req, HTTP_BADREQUEST, "Bad Request"); return; } - headers = evhttp_request_get_output_headers(req); + headers = evhttp_request_get_output_headers(hreq->req); evhttp_add_header(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"); @@ -2889,21 +2887,13 @@ dacp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) 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; +// Forward +static void +dacp_deinit(void); - return 0; -} - -int +static int dacp_init(void) { current_rev = 2; @@ -2920,8 +2910,6 @@ dacp_init(void) dummy_queue_item.album = CFG_NAME_UNKNOWN_ALBUM; dummy_queue_item.genre = CFG_NAME_UNKNOWN_GENRE; - CHECK_ERR(L_DACP, httpd_handlers_set(dacp_handlers)); - #ifdef HAVE_EVENTFD update_efd = eventfd(0, EFD_CLOEXEC); if (update_efd < 0) @@ -2959,7 +2947,7 @@ dacp_init(void) return -1; } -void +static void dacp_deinit(void) { struct dacp_update_request *ur; @@ -2993,6 +2981,16 @@ dacp_deinit(void) close(update_pipe[0]); close(update_pipe[1]); #endif - - httpd_handlers_unset(dacp_handlers); } + +struct httpd_module httpd_dacp = +{ + .name = "DACP", + .type = MODULE_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..f6939509 --- /dev/null +++ b/src/httpd_internal.h @@ -0,0 +1,37 @@ + +#ifndef __HTTPD_INTERNAL_H__ +#define __HTTPD_INTERNAL_H__ + +#include "httpd.h" // TODO remove and transfer + +// 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, +}; + +struct httpd_module +{ + const char *name; + enum httpd_modules type; + char initialized; + + // 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 *hreq); +}; + +#endif /* !__HTTPD_INTERNAL_H__ */ diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 966fe257..4ff4456e 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -41,7 +41,7 @@ #include #include -#include "httpd_jsonapi.h" +#include "httpd_internal.h" #include "conffile.h" #include "db.h" #ifdef LASTFM @@ -4716,24 +4716,24 @@ 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); + DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", hreq->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->req)) { - DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in JSON api request: '%s'\n", uri_parsed->path, uri_parsed->uri); + return; + } - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + if (!hreq->handler) + { + DPRINTF(E_LOG, L_WEB, "Unrecognized JSON API request: '%s'\n", hreq->uri); + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -4742,62 +4742,47 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) 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); + headers = evhttp_request_get_output_headers(hreq->req); evhttp_add_header(headers, "Content-Type", "application/json"); - httpd_send_reply(req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq->req, status_code, "OK", hreq->reply, 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->req, status_code, "No Content", hreq->reply, 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->req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); break; - case HTTP_BADREQUEST: /* 400 Bad Request */ - httpd_send_error(req, status_code, "Bad Request"); + httpd_send_error(hreq->req, status_code, "Bad Request"); break; case 403: - httpd_send_error(req, status_code, "Forbidden"); + httpd_send_error(hreq->req, status_code, "Forbidden"); break; case HTTP_NOTFOUND: /* 404 Not Found */ - httpd_send_error(req, status_code, "Not Found"); + httpd_send_error(hreq->req, status_code, "Not Found"); break; case HTTP_SERVUNAVAIL: /* 503 */ - httpd_send_error(req, status_code, "Service Unavailable"); + httpd_send_error(hreq->req, 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->req, 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 *temp_path; - CHECK_ERR(L_WEB, httpd_handlers_set(adm_handlers)); - default_playlist_directory = NULL; allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists"); if (allow_modifying_stored_playlists) @@ -4824,10 +4809,19 @@ jsonapi_init(void) return 0; } -void +static void jsonapi_deinit(void) { free(default_playlist_directory); - - httpd_handlers_unset(adm_handlers); } + +struct httpd_module httpd_jsonapi = +{ + .name = "JSON API", + .type = MODULE_JSONAPI, + .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_oauth.c b/src/httpd_oauth.c index 29bd5cda..ff41bf76 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" @@ -90,48 +90,28 @@ 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", hreq->uri); - 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->req, 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) -{ - CHECK_ERR(L_WEB, httpd_handlers_set(oauth_handlers)); - - return 0; -} - -void -oauth_deinit(void) -{ - httpd_handlers_unset(oauth_handlers); -} + .name = "OAuth", + .type = MODULE_OAUTH, + .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 da67a781..af10f445 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" @@ -863,57 +862,46 @@ 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); + DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", hreq->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->req, "Server error"); return; } ret = rsp_request_authorize(hreq); if (ret < 0) { - rsp_send_error(req, "Access denied"); + rsp_send_error(hreq->req, "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) { snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE); - CHECK_ERR(L_RSP, httpd_handlers_set(rsp_handlers)); - return 0; } -void -rsp_deinit(void) +struct httpd_module httpd_rsp = { - httpd_handlers_unset(rsp_handlers); -} + .name = "RSP", + .type = MODULE_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..09075336 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -32,6 +32,7 @@ #include +#include "httpd_internal.h" #include "httpd_streaming.h" #include "logger.h" #include "conffile.h" @@ -523,8 +524,27 @@ streaming_write(struct output_buffer *obuf) } } -int -streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +// Since streaming is a one-trick pony it doesn't need handlers +static int +streaming_dummy_handler(struct httpd_request *hreq) +{ + return 0; +} + +static struct httpd_uri_map streaming_handlers[] = + { + { + .regexp = "^/stream.mp3$", + .handler = streaming_dummy_handler + }, + { + .regexp = NULL, + .handler = NULL + } + }; + +static void +streaming_request(struct httpd_request *hreq) { struct streaming_session *session; struct evhttp_connection *evcon; @@ -541,13 +561,13 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse { 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; + evhttp_send_error(hreq->req, HTTP_NOTFOUND, "Not Found"); + return; } - evcon = evhttp_request_get_connection(req); - httpd_peer_get(&address, &port, evcon); - param = evhttp_find_header( evhttp_request_get_input_headers(req), "Icy-MetaData"); + evcon = evhttp_request_get_connection(hreq->req); + evhttp_connection_get_peer(evcon, &address, &port); + param = evhttp_find_header( evhttp_request_get_input_headers(hreq->req), "Icy-MetaData"); if (param && strcmp(param, "1") == 0) require_icy = true; @@ -556,7 +576,7 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse lib = cfg_getsec(cfg, "library"); name = cfg_getstr(lib, "name"); - output_headers = evhttp_request_get_output_headers(req); + output_headers = evhttp_request_get_output_headers(hreq->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"); @@ -572,15 +592,15 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse 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"); + evhttp_send_reply_start(hreq->req, HTTP_OK, "OK"); session = calloc(1, sizeof(struct streaming_session)); if (!session) { DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n"); - evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - return -1; + evhttp_send_error(hreq->req, HTTP_SERVUNAVAIL, "Internal Server Error"); + return; } pthread_mutex_lock(&streaming_sessions_lck); @@ -591,7 +611,7 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse event_add(metaev, NULL); } - session->req = req; + session->req = hreq->req; session->next = streaming_sessions; session->require_icy = require_icy; session->bytes_sent = 0; @@ -600,23 +620,9 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse pthread_mutex_unlock(&streaming_sessions_lck); evhttp_connection_set_closecb(evcon, streaming_close_cb, session); - - return 0; } -int -streaming_is_request(const char *path) -{ - char *ptr; - - ptr = strrchr(path, '/'); - if (ptr && (strcasecmp(ptr, "/stream.mp3") == 0)) - return 1; - - return 0; -} - -int +static int streaming_init(void) { int ret; @@ -723,7 +729,7 @@ streaming_init(void) return -1; } -void +static void streaming_deinit(void) { streaming_end(); @@ -744,3 +750,14 @@ streaming_deinit(void) pthread_mutex_destroy(&streaming_sessions_lck); } + +struct httpd_module httpd_streaming = +{ + .name = "Streaming", + .type = MODULE_STREAMING, + .fullpaths = { "/stream.mp3", NULL }, + .handlers = streaming_handlers, + .init = streaming_init, + .deinit = streaming_deinit, + .request = streaming_request, +}; diff --git a/src/httpd_streaming.h b/src/httpd_streaming.h index 3df82023..e32d6094 100644 --- a/src/httpd_streaming.h +++ b/src/httpd_streaming.h @@ -2,7 +2,6 @@ #ifndef __HTTPD_STREAMING_H__ #define __HTTPD_STREAMING_H__ -#include "httpd.h" #include "outputs.h" /* httpd_streaming takes care of incoming requests to /stream.mp3 @@ -14,16 +13,4 @@ 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__ */ From 4ae73fa9b4cf284144f257f5448fe2f17ebd9bc4 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 21 Dec 2022 18:09:09 +0100 Subject: [PATCH 08/21] [httpd] Move internal declarations to httpd_internal.h --- src/httpd.h | 151 ------------------------------------------ src/httpd_internal.h | 153 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 152 deletions(-) diff --git a/src/httpd.h b/src/httpd.h index 04d965a9..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 - -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; - // TODO - const char *uri; - // 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); - void *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. Nothing is copied, so 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. - */ -int -httpd_request_parse(struct httpd_request *hreq, 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_internal.h b/src/httpd_internal.h index f6939509..e9b62483 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -2,7 +2,71 @@ #ifndef __HTTPD_INTERNAL_H__ #define __HTTPD_INTERNAL_H__ -#include "httpd.h" // TODO remove and transfer +#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; + // Shortcut to &uri_parsed->uri + const char *uri; + // 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) + 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); +}; + + +/*---------------------------------- MODULES ---------------------------------*/ // Must be in sync with modules[] in httpd.c enum httpd_modules @@ -34,4 +98,91 @@ struct httpd_module void (*request)(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); + void *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); + +void +httpd_stream_file(struct evhttp_request *req, int id); + +/* + * Parse a request into the httpd_request struct. Nothing is copied, so 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. + */ +int +httpd_request_parse(struct httpd_request *hreq, struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map); + +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); + +/* + * 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); + #endif /* !__HTTPD_INTERNAL_H__ */ From 74f1b93b425cd7f8e04610a0278efb1c08d60a50 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 21 Dec 2022 19:11:03 +0100 Subject: [PATCH 09/21] [httpd] Make http modules agnostic to evhttp --- configure.ac | 5 + src/Makefile.am | 7 + src/httpd.c | 776 ++++++++++++++++------------------------- src/httpd_artworkapi.c | 56 ++- src/httpd_daap.c | 402 ++++++++++----------- src/httpd_dacp.c | 444 +++++++++++------------ src/httpd_internal.h | 364 ++++++++++++++----- src/httpd_jsonapi.c | 481 ++++++++++++------------- src/httpd_libevhtp.c | 463 ++++++++++++++++++++++++ src/httpd_libevhttp.c | 350 +++++++++++++++++++ src/httpd_oauth.c | 13 +- src/httpd_rsp.c | 100 +++--- src/httpd_streaming.c | 84 ++--- src/misc.c | 2 + src/misc.h | 4 +- 15 files changed, 2152 insertions(+), 1399 deletions(-) create mode 100644 src/httpd_libevhtp.c create mode 100644 src/httpd_libevhttp.c diff --git a/configure.ac b/configure.ac index 16822734..d3f4eed6 100644 --- a/configure.ac +++ b/configure.ac @@ -268,6 +268,11 @@ OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevent_pthreads support], [libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads], [evthread_use_pthreads], [event2/thread.h]) +dnl Build with libevhtp +OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevhtp support], [libevhtp], [LIBEVHTP], + [evhtp]) +AM_CONDITIONAL([COND_LIBEVHTP], [[test "x$with_libevhtp" = "xyes"]]) + 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/src/Makefile.am b/src/Makefile.am index 00c0f83a..d468d831 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,12 @@ if COND_LIBWEBSOCKETS LIBWEBSOCKETS_SRC=websocket.c websocket.h endif +if COND_LIBEVHTP +HTTPDBACKEND_SRC=httpd_libevhtp.c +else +HTTPDBACKEND_SRC=httpd_libevhttp.c +endif + GPERF_FILES = \ daap_query.gperf \ dacp_prop.gperf \ @@ -92,6 +98,7 @@ owntone_SOURCES = main.c \ library.c library.h \ $(MDNS_SRC) mdns.h \ remote_pairing.c remote_pairing.h \ + $(HTTPDBACKEND_SRC) \ httpd.c httpd.h httpd_internal.h \ httpd_rsp.c \ httpd_daap.c httpd_daap.h \ diff --git a/src/httpd.c b/src/httpd.c index 5d0f3a4f..970b1b03 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -36,12 +36,12 @@ #include #include +#include // get thread ID + #ifdef HAVE_EVENTFD # include #endif #include -#include -#include #include #include @@ -99,7 +99,7 @@ struct content_type_map { }; struct stream_ctx { - struct evhttp_request *req; + struct httpd_request *hreq; uint8_t *buf; struct evbuffer *evbuf; struct event *ev; @@ -128,7 +128,7 @@ static const struct content_type_map ext2ctype[] = }; static char webroot_directory[PATH_MAX]; -struct event_base *evbase_httpd; +static struct event_base *evbase_httpd; #ifdef HAVE_EVENTFD static int exit_efd; @@ -137,10 +137,10 @@ static int exit_pipe[2]; #endif static int httpd_exit; static struct event *exitev; -static struct evhttp *evhttpd; +static httpd_server *httpd_serv; static pthread_t tid_httpd; -static const char *allow_origin; +static const char *httpd_allow_origin; static int httpd_port; @@ -220,7 +220,7 @@ modules_handlers_set(struct httpd_uri_map *uri_map) } static int -modules_init(void) +modules_init(struct event_base *evbase) { struct httpd_module **ptr; struct httpd_module *m; @@ -228,7 +228,7 @@ modules_init(void) for (ptr = httpd_modules; *ptr; ptr++) { m = *ptr; - m->initialized = (!m->init || m->init() == 0); + m->initialized = (!m->init || m->init(evbase) == 0); if (!m->initialized) { DPRINTF(E_FATAL, L_HTTPD, "%s init failed\n", m->name); @@ -291,15 +291,93 @@ modules_search(const char *path) /* --------------------------- REQUEST HELPERS ------------------------------ */ -void -httpd_redirect_to(struct evhttp_request *req, const char *path) +static void +request_unset(struct httpd_request *hreq) { - struct evkeyvalq *headers; + if (hreq->out_body) + evbuffer_free(hreq->out_body); - headers = evhttp_request_get_output_headers(req); - evhttp_add_header(headers, "Location", path); + httpd_uri_parsed_free(hreq->uri_parsed); + httpd_backend_data_free(hreq->backend_data); +} - httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP); +static void +request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, const char *user_agent) +{ + httpd_backend_data *backend_data; + struct httpd_uri_map *map; + int ret; + + memset(hreq, 0, sizeof(struct httpd_request)); + + // Populate hreq by getting values from the backend (or from the caller) + hreq->backend = backend; + if (backend) + { + backend_data = httpd_backend_data_create(backend); + 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); + return; + } + + // 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); + + // Path with e.g. /api -> JSON module + hreq->module = modules_search(hreq->path); + if (!hreq->module) + { + return; + } + + 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; + 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", NULL, HTTPD_SEND_NO_GZIP); } /* @@ -314,23 +392,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; } @@ -347,16 +421,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)); @@ -365,38 +436,32 @@ 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; @@ -405,16 +470,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; } @@ -422,7 +486,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,8 +494,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; } @@ -440,8 +503,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; } @@ -454,7 +516,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; } @@ -462,7 +524,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; } @@ -471,7 +533,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; } } @@ -480,14 +542,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, 403, "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); + httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); return; } @@ -496,7 +558,7 @@ serve_file(struct evhttp_request *req, const char *uri) { DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n"); - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal error"); return; } @@ -505,7 +567,7 @@ 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"); + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); evbuffer_free(evbuf); return; } @@ -540,17 +602,16 @@ 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", evbuf, HTTPD_SEND_NO_GZIP); evbuffer_free(evbuf); close(fd); return; out_fail: - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal error"); evbuffer_free(evbuf); close(fd); } @@ -561,15 +622,10 @@ serve_file(struct evhttp_request *req, const char *uri) static void stream_end(struct stream_ctx *st, int failed) { - struct evhttp_connection *evcon; - - evcon = evhttp_request_get_connection(st->req); - - if (evcon) - evhttp_connection_set_closecb(evcon, NULL, NULL); + httpd_request_closecb_set(st->hreq, NULL, NULL); if (!failed) - evhttp_send_reply_end(st->req); + httpd_send_reply_end(st->hreq); evbuffer_free(st->evbuf); event_free(st->ev); @@ -601,7 +657,7 @@ stream_end_register(struct stream_ctx *st) } static void -stream_chunk_resched_cb(struct evhttp_connection *evcon, void *arg) +stream_chunk_resched_cb(httpd_connection *conn, void *arg) { struct stream_ctx *st; struct timeval tv; @@ -666,7 +722,7 @@ stream_chunk_xcode_cb(int fd, short event, void *arg) else ret = xcoded; - evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st); + httpd_send_reply_chunk(st->hreq, st->evbuf, stream_chunk_resched_cb, st); st->offset += ret; @@ -722,7 +778,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) evbuffer_add(st->evbuf, st->buf, ret); - evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st); + httpd_send_reply_chunk(st->hreq, st->evbuf, stream_chunk_resched_cb, st); st->offset += ret; @@ -730,7 +786,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) } static void -stream_fail_cb(struct evhttp_connection *evcon, void *arg) +stream_fail_cb(httpd_connection *conn, void *arg) { struct stream_ctx *st; @@ -778,238 +834,91 @@ exit_cb(int fd, short event, void *arg) httpd_exit = 1; } -static void -httpd_gen_cb(struct evhttp_request *req, void *arg) +static int +handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) { - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; - struct httpd_request hreq; - struct httpd_uri_parsed *parsed; - struct httpd_module *m; - const char *uri; + bool is_cors_preflight; - // 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; + is_cors_preflight = ( 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") ); + if (!is_cors_preflight) + return -1; + + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", 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"); + + // In this case there is no reason to go through httpd_send_reply + httpd_backend_reply_send(hreq->backend, HTTP_OK, "OK", NULL); + return 0; +} + +static void +httpd_gen_cb(httpd_backend *backend, void *arg) +{ + struct httpd_request hreq_stack; + struct httpd_request *hreq = &hreq_stack; // Shorthand + + // This is to make modifications to e.g. evhttps's request object + httpd_backend_preprocess(backend); + + // Populates the hreq struct + request_set(hreq, backend, NULL, NULL); // 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") ) + if (handle_cors_preflight(hreq, httpd_allow_origin) == 0) { - 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); - return; - } - - uri = evhttp_request_get_uri(req); - if (!uri) - { - DPRINTF(E_WARN, L_HTTPD, "No URI in request\n"); - httpd_redirect_to(req, "/"); - return; - } - - parsed = httpd_uri_parse(uri); - if (!parsed || !parsed->path) - { - httpd_redirect_to(req, "/"); goto out; } - if (strcmp(parsed->path, "/") == 0) + if (!hreq->uri || !hreq->uri_parsed) { - goto serve_file; + DPRINTF(E_WARN, L_HTTPD, "Invalid URI in request: '%s'\n", hreq->uri); + httpd_redirect_to(hreq, "/"); + goto out; } - - m = modules_search(parsed->path); - if (m) + else if (!hreq->path) { - httpd_request_parse(&hreq, req, parsed, NULL, m->handlers); - m->request(&hreq); + DPRINTF(E_WARN, L_HTTPD, "Invalid path in request: '%s'\n", hreq->uri); + httpd_redirect_to(hreq, "/"); goto out; } - DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri); - - /* Serve web interface files */ - serve_file: - serve_file(req, parsed->path); + if (hreq->module) + { + DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s' (thread %ld)\n", hreq->module->name, hreq->uri, syscall(SYS_gettid)); + hreq->module->request(hreq); + } + else + { + // Serve web interface files + DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", hreq->uri); + serve_file(hreq); + } out: - httpd_uri_free(parsed); + request_unset(hreq); } /* ------------------------------- HTTPD API -------------------------------- */ void -httpd_uri_free(struct httpd_uri_parsed *parsed) +httpd_request_unset(struct httpd_request *hreq) { - 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); + request_unset(hreq); } -struct httpd_uri_parsed * -httpd_uri_parse(const char *uri) +void +httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent) { - 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; -} - -int -httpd_request_parse(struct httpd_request *hreq, struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map) -{ - ; - struct evhttp_connection *evcon; - struct evkeyvalq *headers; - struct httpd_uri_map *uri; - int req_method; - int ret; - - memset(hreq, 0, sizeof(struct httpd_request)); - - // Note req is allowed to be NULL - hreq->req = req; - hreq->uri = uri_parsed->uri; - 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 (uri = uri_map; uri->handler; uri++) - { - // Check if handler supports the current http request method - if (uri->method && req_method && !(req_method & uri->method)) - continue; - - ret = regexec(uri->preg, uri_parsed->path, 0, NULL, 0); - if (ret != 0) - continue; - - hreq->handler = uri->handler; - return 0; // Success - } - - // Handler not found, that's an error - return -1; + request_set(hreq, NULL, uri, user_agent); } /* Thread: httpd */ void -httpd_stream_file(struct evhttp_request *req, int id) +httpd_stream_file(struct httpd_request *hreq, int id) { struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 }; struct media_file_info *mfi; @@ -1017,12 +926,8 @@ httpd_stream_file(struct evhttp_request *req, int id) 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; const char *param; const char *param_end; - const char *ua; const char *client_codecs; char buf[64]; int64_t offset; @@ -1034,9 +939,7 @@ httpd_stream_file(struct evhttp_request *req, int id) 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); @@ -1075,7 +978,7 @@ 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"); + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); return; } @@ -1083,7 +986,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { 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"); + httpd_send_error(hreq, 500, "Cannot stream non-file content"); goto out_free_mfi; } @@ -1092,18 +995,15 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_LOG, L_HTTPD, "Out of memory for struct stream_ctx\n"); - evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); + httpd_send_error(hreq, 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"); + client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs"); - transcode = transcode_needed(ua, client_codecs, mfi->codectype); - - output_headers = evhttp_request_get_output_headers(req); + transcode = transcode_needed(hreq->user_agent, client_codecs, mfi->codectype); if (transcode) { @@ -1116,13 +1016,12 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n"); - evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - + httpd_send_error(hreq, 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 { @@ -1134,8 +1033,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_LOG, L_HTTPD, "Out of memory for raw streaming buffer\n"); - evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); goto out_free_st; } @@ -1146,8 +1044,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno)); - evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); - + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); goto out_cleanup; } @@ -1156,8 +1053,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno)); - evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); - + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); goto out_cleanup; } st->size = sb.st_size; @@ -1167,8 +1063,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno)); - evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); - + httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request"); goto out_cleanup; } st->offset = offset; @@ -1186,21 +1081,21 @@ httpd_stream_file(struct evhttp_request *req, int id) 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) + 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); } } @@ -1209,9 +1104,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { 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"); - + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); goto out_cleanup; } @@ -1220,9 +1113,7 @@ httpd_stream_file(struct evhttp_request *req, int id) { 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"); - + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); goto out_cleanup; } @@ -1232,16 +1123,14 @@ httpd_stream_file(struct evhttp_request *req, int id) { 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"); - + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); goto out_cleanup; } st->id = mfi->id; st->start_offset = offset; st->stream_size = st->size; - st->req = req; + st->hreq = hreq; if ((offset == 0) && (end_offset == 0)) { @@ -1255,10 +1144,10 @@ httpd_stream_file(struct evhttp_request *req, int id) 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 { @@ -1274,15 +1163,15 @@ 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 @@ -1296,9 +1185,7 @@ 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_closecb_set(hreq, stream_fail_cb, st); DPRINTF(E_INFO, L_HTTPD, "Kicking off streaming for %s\n", mfi->path); @@ -1389,35 +1276,30 @@ httpd_gzip_deflate(struct evbuffer *in) } 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, struct evbuffer *evbuf, enum httpd_send_flags flags) { struct evbuffer *gzbuf; - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; 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")) && + (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); + if (httpd_allow_origin) + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf))) { DPRINTF(E_DBG, L_HTTPD, "Gzipping response\n"); - evhttp_add_header(output_headers, "Content-Encoding", "gzip"); - evhttp_send_reply(req, code, reason, gzbuf); + httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); + httpd_backend_reply_send(hreq->backend, code, reason, gzbuf); evbuffer_free(gzbuf); // Drain original buffer, as would be after evhttp_send_reply() @@ -1425,30 +1307,40 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc } else { - evhttp_send_reply(req, code, reason, evbuf); + httpd_backend_reply_send(hreq->backend, code, reason, evbuf); } } +void +httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason) +{ + httpd_backend_reply_start_send(hreq->backend, code, reason); +} + +void +httpd_send_reply_chunk(struct httpd_request *hreq, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) +{ + httpd_backend_reply_chunk_send(hreq->backend, evbuf, cb, arg); +} + +void +httpd_send_reply_end(struct httpd_request *hreq) +{ + httpd_backend_reply_end_send(hreq->backend); +} + // 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; - if (!allow_origin) - { - evhttp_send_error(req, error, reason); - return; - } + httpd_headers_clear(hreq->out_headers); - output_headers = evhttp_request_get_output_headers(req); - - evhttp_clear_headers(output_headers); - - 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"); + if (httpd_allow_origin) + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); + httpd_header_add(hreq->out_headers, "Content-Type", "text/html"); + httpd_header_add(hreq->out_headers, "Connection", "close"); evbuf = evbuffer_new(); if (!evbuf) @@ -1456,48 +1348,36 @@ httpd_send_error(struct evhttp_request* req, int error, const char* reason) else evbuffer_add_printf(evbuf, ERR_PAGE, error, reason, reason); - evhttp_send_reply(req, error, reason, evbuf); + httpd_backend_reply_send(hreq->backend, error, reason, evbuf); if (evbuf) evbuffer_free(evbuf); } 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, 403, "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; @@ -1509,18 +1389,16 @@ 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"); @@ -1584,23 +1462,22 @@ 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"); + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); return -1; } - headers = evhttp_request_get_output_headers(req); - evhttp_add_header(headers, "WWW-Authenticate", header); + httpd_header_add(hreq->out_headers, "WWW-Authenticate", header); evbuffer_add_printf(evbuf, ERR_PAGE, 401, "Unauthorized", "Authorization required"); - httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP); evbuffer_free(evbuf); @@ -1622,37 +1499,63 @@ int httpd_init(const char *webroot) { struct stat sb; + int exit_fd; 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"); + CHECK_NULL(L_HTTPD, evbase_httpd = event_base_new()); - return -1; +#ifdef HAVE_EVENTFD + CHECK_ERRNO(L_HTTPD, exit_efd = eventfd(0, EFD_CLOEXEC)); + exit_fd = exit_efd; +#else +# ifdef HAVE_PIPE2 + CHECK_ERRNO(L_HTTPD, pipe2(exit_pipe, O_CLOEXEC)); +# else + CHECK_ERRNO(L_HTTPD, pipe(exit_pipe)); +# endif + exit_fd = exit_pipe[0]; +#endif /* HAVE_EVENTFD */ + CHECK_NULL(L_HTTPD, exitev = event_new(evbase_httpd, exit_fd, EV_READ, exit_cb, NULL)); + event_add(exitev, NULL); + + httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); + httpd_serv = httpd_server_new(evbase_httpd, httpd_port, httpd_gen_cb, NULL); + if (!httpd_serv) + { + DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); + goto server_fail; + } + + // For CORS headers + httpd_allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin"); + if (strlen(httpd_allow_origin) == 0) + httpd_allow_origin = NULL; + httpd_server_allow_origin_set(httpd_serv, httpd_allow_origin); + + // Prepare modules, e.g. httpd_daap + ret = modules_init(evbase_httpd); + if (ret < 0) + { + DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n"); + goto modules_fail; } #ifdef HAVE_LIBWEBSOCKETS @@ -1660,86 +1563,14 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Websocket init failed\n"); - goto websocket_fail; } #endif - ret = modules_init(); - if (ret < 0) - { - DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n"); - - goto modules_fail; - } - -#ifdef HAVE_EVENTFD - exit_efd = eventfd(0, EFD_CLOEXEC); - if (exit_efd < 0) - { - DPRINTF(E_FATAL, L_HTTPD, "Could not create eventfd: %s\n", strerror(errno)); - - goto pipe_fail; - } - - 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 - if (ret < 0) - { - DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno)); - - goto pipe_fail; - } - - 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; } @@ -1748,23 +1579,20 @@ httpd_init(const char *webroot) return 0; thread_fail: - bind_fail: - evhttp_free(evhttpd); - evhttpd_fail: +#ifdef HAVE_LIBWEBSOCKETS + websocket_deinit(); + websocket_fail: +#endif + modules_deinit(); + modules_fail: + httpd_server_free(httpd_serv); + server_fail: event_free(exitev); - exitev_fail: #ifdef HAVE_EVENTFD close(exit_efd); #else close(exit_pipe[0]); close(exit_pipe[1]); -#endif - pipe_fail: - modules_fail: - modules_deinit(); -#ifdef HAVE_LIBWEBSOCKETS - websocket_deinit(); - websocket_fail: #endif event_base_free(evbase_httpd); @@ -1818,6 +1646,6 @@ httpd_deinit(void) close(exit_pipe[1]); #endif event_free(exitev); - evhttp_free(evhttpd); + httpd_server_free(httpd_serv); event_base_free(evbase_httpd); } diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index 61cca7a9..d1292f87 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -20,7 +20,6 @@ # include #endif -#include #include #include #include @@ -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,20 +125,20 @@ 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 } }; @@ -155,52 +150,47 @@ artworkapi_request(struct httpd_request *hreq) { int status_code; - DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", hreq->uri); - - if (!httpd_admin_check_auth(hreq->req)) + if (!httpd_admin_check_auth(hreq)) return; if (!hreq->handler) { DPRINTF(E_LOG, L_WEB, "Unrecognized path in artwork api request: '%s'\n", hreq->uri); - httpd_send_error(hreq->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(hreq->req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, status_code, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case HTTP_NOCONTENT: /* 204 No Content */ - httpd_send_reply(hreq->req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, status_code, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case HTTP_NOTMODIFIED: /* 304 Not Modified */ - httpd_send_reply(hreq->req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); break; case HTTP_BADREQUEST: /* 400 Bad Request */ - httpd_send_error(hreq->req, status_code, "Bad Request"); + httpd_send_error(hreq, status_code, "Bad Request"); break; case HTTP_NOTFOUND: /* 404 Not Found */ - httpd_send_error(hreq->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(hreq->req, HTTP_INTERNAL, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); } - - evbuffer_free(hreq->reply); } struct httpd_module httpd_artworkapi = { .name = "Artwork API", .type = MODULE_ARTWORKAPI, + .logdomain = L_WEB, .subpaths = { "/artwork/", NULL }, .handlers = artworkapi_handlers, .request = artworkapi_request, diff --git a/src/httpd_daap.c b/src/httpd_daap.c index e6663802..640a5bd9 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -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; @@ -286,7 +283,6 @@ 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; @@ -301,29 +297,25 @@ update_refresh_cb(int fd, short event, void *arg) dmap_add_int(reply, "mstt", 200); /* 12 */ dmap_add_int(reply, "musr", current_rev); /* 12 */ - evcon = evhttp_request_get_connection(ur->req); - evhttp_connection_set_closecb(evcon, NULL, NULL); + httpd_request_closecb_set(ur->hreq, NULL, NULL); - httpd_send_reply(ur->req, HTTP_OK, "OK", reply, 0); + httpd_send_reply(ur->hreq, HTTP_OK, "OK", reply, 0); update_remove(ur); } static void -update_fail_cb(struct evhttp_connection *evcon, void *arg) +update_fail_cb(httpd_connection *conn, void *arg) { - struct evhttp_connection *evc; struct daap_update_request *ur; ur = (struct daap_update_request *)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); + httpd_request_closecb_set(ur->hreq, NULL, NULL); - evhttp_request_free(ur->req); + httpd_request_backend_free(ur->hreq); // TODO check if still necessary update_remove(ur); } @@ -513,7 +505,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 +551,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 +572,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 +580,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 +667,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, 204, "Logout Successful", hreq->out_body, 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", hreq->out_body, 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", hreq->out_body, 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", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case DAAP_REPLY_FORBIDDEN: - httpd_send_error(hreq->req, 403, "Forbidden"); + httpd_send_error(hreq, 403, "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 +710,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, 401, "Unauthorized"); return -1; } @@ -741,16 +733,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 +756,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 httpd_request_connection_get(hreq) 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 +769,7 @@ daap_reply_server_info(struct httpd_request *hreq) int mpro; int apro; - if (!hreq->req) + if (!httpd_request_connection_get(hreq)) { 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 +784,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 +854,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 +877,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 +905,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 +938,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 +954,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,18 +982,18 @@ static enum daap_reply_result daap_reply_update(struct httpd_request *hreq) { struct daap_update_request *ur; - struct evhttp_connection *evcon; + struct event_base *evbase; const char *param; int reqd_rev; int ret; - if (!hreq->req) + if (!httpd_request_connection_get(hreq)) { 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"); @@ -1017,18 +1007,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; } @@ -1039,13 +1029,15 @@ 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); + evbase = httpd_request_evbase_get(hreq); + + ur->timeout = evtimer_new(evbase, update_refresh_cb, ur); if (ur->timeout) ret = evtimer_add(ur->timeout, &daap_update_refresh_tv); else @@ -1055,14 +1047,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; @@ -1070,9 +1062,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); + httpd_request_closecb_set(hreq, update_fail_cb, ur); return DAAP_REPLY_NONE; } @@ -1100,7 +1090,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); @@ -1143,14 +1133,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); @@ -1165,7 +1155,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; @@ -1206,11 +1195,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"); @@ -1234,15 +1223,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; @@ -1300,13 +1288,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; } @@ -1315,25 +1303,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); @@ -1366,10 +1354,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; } @@ -1412,10 +1400,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; } @@ -1424,11 +1412,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"); @@ -1441,7 +1429,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; } @@ -1450,7 +1438,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; } @@ -1566,26 +1554,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); @@ -1626,7 +1614,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: @@ -1650,11 +1638,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"); @@ -1667,7 +1655,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; } @@ -1676,7 +1664,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; } @@ -1768,13 +1756,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; } @@ -1783,25 +1771,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); @@ -1836,36 +1824,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); @@ -1873,7 +1861,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; } @@ -1901,7 +1889,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; } @@ -1909,24 +1897,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); @@ -1947,7 +1935,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; @@ -1957,22 +1944,22 @@ daap_reply_extra_data(struct httpd_request *hreq) int max_h; int ret; - if (!hreq->req) + if (!httpd_request_connection_get(hreq)) { 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) { @@ -1980,7 +1967,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) { @@ -1996,12 +1983,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) { @@ -2015,16 +2002,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; @@ -2038,17 +2024,17 @@ daap_stream(struct httpd_request *hreq) int id; int ret; - if (!hreq->req) + if (!httpd_request_connection_get(hreq)) { 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; } @@ -2126,16 +2112,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; } @@ -2228,7 +2214,6 @@ static struct httpd_uri_map daap_handlers[] = static void daap_request(struct httpd_request *hreq) { - struct evkeyvalq *headers; struct timespec start; struct timespec end; struct daap_session session; @@ -2237,18 +2222,16 @@ daap_request(struct httpd_request *hreq) int ret; int msec; - DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", hreq->uri); - if (!hreq->handler) { DPRINTF(E_LOG, L_DAAP, "Unrecognized path in DAAP request: '%s'\n", hreq->uri); - httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(hreq, HTTP_BADREQUEST, "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); @@ -2262,7 +2245,7 @@ daap_request(struct httpd_request *hreq) 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; } @@ -2273,26 +2256,20 @@ daap_request(struct httpd_request *hreq) } // Set reply headers - headers = evhttp_request_get_output_headers(hreq->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, hreq->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(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply - - evbuffer_free(hreq->reply); + httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); + httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); // TODO not all want this reply return; } @@ -2310,8 +2287,6 @@ daap_request(struct httpd_request *hreq) if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold() && hreq->user_agent) cache_daap_add(hreq->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec); - - evbuffer_free(hreq->reply); } int @@ -2332,23 +2307,18 @@ 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; struct daap_session session; int ret; DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri); - reply = NULL; + out_body = NULL; - uri_parsed = httpd_uri_parse(uri); - if (!uri_parsed) - return NULL; - - ret = httpd_request_parse(&hreq, NULL, uri_parsed, user_agent, daap_handlers); - if (ret < 0) + httpd_request_set(&hreq, uri, user_agent); + if (!(&hreq)->handler) { - DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path '%s' in request: '%s'\n", uri_parsed->path, uri_parsed->uri); + DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path in request: '%s'\n", uri); goto out; } @@ -2357,29 +2327,27 @@ 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; } - reply = hreq.reply; + // Take ownership of the reply + out_body = hreq.out_body; + hreq.out_body = NULL; out: - httpd_uri_free(uri_parsed); + httpd_request_unset(&hreq); - return reply; + return out_body; } static int -daap_init(void) +daap_init(struct event_base *evbase) { srand((unsigned)time(NULL)); current_rev = 2; - update_requests = NULL; return 0; } @@ -2389,7 +2357,7 @@ daap_deinit(void) { struct daap_session *s; struct daap_update_request *ur; - struct evhttp_connection *evcon; + httpd_connection *conn; for (s = daap_sessions; daap_sessions; s = daap_sessions) { @@ -2401,12 +2369,9 @@ 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); - } + httpd_request_closecb_set(ur->hreq, NULL, NULL); + conn = httpd_request_connection_get(ur->hreq); + httpd_connection_free(conn); // TODO necessary? update_free(ur); } @@ -2416,6 +2381,7 @@ 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 }, diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 09d97c30..f4c3be4c 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -50,11 +49,8 @@ #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 dacp_update_request *next; }; @@ -155,11 +151,11 @@ static struct db_queue_item dummy_queue_item; /* -------------------------------- HELPERS --------------------------------- */ static void -dacp_send_error(struct evhttp_request *req, const char *container, const char *errmsg) +dacp_send_error(struct httpd_request *hreq, const char *container, const char *errmsg) { struct evbuffer *evbuf; - if (!req) + if (!hreq) return; evbuf = evbuffer_new(); @@ -167,13 +163,13 @@ dacp_send_error(struct evhttp_request *req, const char *container, const char *e { DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for DMAP error\n"); - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); return; } dmap_error_make(evbuf, container, errmsg); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); evbuffer_free(evbuf); } @@ -565,15 +561,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; @@ -633,7 +627,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"); @@ -656,9 +650,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, 403, "Forbidden"); return -1; } @@ -737,7 +731,6 @@ playstatusupdate_cb(int fd, short what, void *arg) struct dacp_update_request *ur; struct evbuffer *evbuf; struct evbuffer *update; - struct evhttp_connection *evcon; uint8_t *buf; size_t len; int ret; @@ -776,19 +769,17 @@ playstatusupdate_cb(int fd, short what, void *arg) { update_requests = ur->next; - evcon = evhttp_request_get_connection(ur->req); - if (evcon) - evhttp_connection_set_closecb(evcon, NULL, NULL); + httpd_request_closecb_set(ur->hreq, 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); + httpd_send_reply(ur->hreq, HTTP_OK, "OK", evbuf, 0); } else - httpd_send_reply(ur->req, HTTP_OK, "OK", update, 0); + httpd_send_reply(ur->hreq, HTTP_OK, "OK", update, 0); free(ur); } @@ -822,19 +813,16 @@ dacp_playstatus_update_handler(short event_mask) } static void -update_fail_cb(struct evhttp_connection *evcon, void *arg) +update_fail_cb(httpd_connection *conn, void *arg) { 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); + httpd_request_closecb_set(ur->hreq, NULL, NULL); if (ur == update_requests) update_requests = ur->next; @@ -852,7 +840,7 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg) p->next = ur->next; } - evhttp_request_free(ur->req); + httpd_request_backend_free(ur->hreq); // TODO check if still necessary free(ur); } @@ -978,7 +966,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); @@ -993,7 +981,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); @@ -1125,9 +1113,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) { @@ -1192,33 +1180,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", hreq->out_body, 0); return 0; } @@ -1238,7 +1226,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); @@ -1254,17 +1242,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"); - dacp_send_error(hreq->req, "cacr", "Could not build song queue"); + dacp_send_error(hreq, "cacr", "Could not build song queue"); return -1; } } @@ -1273,12 +1261,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); @@ -1287,10 +1275,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 */ @@ -1304,7 +1292,7 @@ dacp_reply_cue_play(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); - dacp_send_error(hreq->req, "cacr", "Playback failed to start"); + dacp_send_error(hreq, "cacr", "Playback failed to start"); return -1; } } @@ -1312,7 +1300,7 @@ dacp_reply_cue_play(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); - dacp_send_error(hreq->req, "cacr", "Playback failed to start"); + dacp_send_error(hreq, "cacr", "Playback failed to start"); return -1; } } @@ -1327,7 +1315,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); - dacp_send_error(hreq->req, "cacr", "Playback failed to start"); + dacp_send_error(hreq, "cacr", "Playback failed to start"); return -1; } } @@ -1339,7 +1327,7 @@ dacp_reply_cue_play(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d\n", pos); - dacp_send_error(hreq->req, "cacr", "Playback failed to start"); + dacp_send_error(hreq, "cacr", "Playback failed to start"); return -1; } } @@ -1350,19 +1338,19 @@ dacp_reply_cue_play(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - dacp_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", hreq->out_body, 0); return 0; } @@ -1376,13 +1364,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", hreq->out_body, 0); return 0; } @@ -1397,12 +1385,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"); - dacp_send_error(hreq->req, "cacr", "No command in cue request"); + dacp_send_error(hreq, "cacr", "No command in cue request"); return -1; } @@ -1414,7 +1402,7 @@ dacp_reply_cue(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Unknown cue command %s\n", param); - dacp_send_error(hreq->req, "cacr", "Unknown command in cue request"); + dacp_send_error(hreq, "cacr", "Unknown command in cue request"); return -1; } } @@ -1431,12 +1419,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1463,10 +1451,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"); @@ -1495,9 +1483,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; @@ -1568,11 +1556,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; out_fail: - httpd_send_error(hreq->req, 500, "Internal Server Error"); + httpd_send_error(hreq, 500, "Internal Server Error"); return -1; } @@ -1589,7 +1577,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1606,7 +1594,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1633,13 +1621,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1658,7 +1646,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, 500, "Internal Server Error"); return -1; } @@ -1667,12 +1655,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1691,7 +1679,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, 500, "Internal Server Error"); return -1; } @@ -1700,12 +1688,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1722,7 +1710,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1739,7 +1727,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1756,7 +1744,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -1787,7 +1775,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); @@ -1796,7 +1784,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); @@ -1891,24 +1879,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", hreq->out_body, 0); return 0; @@ -1916,7 +1904,7 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq) DPRINTF(E_LOG, L_DACP, "Database error in dacp_reply_playqueuecontents\n"); evbuffer_free(songlist); - dacp_send_error(hreq->req, "ceQR", "Database error"); + dacp_send_error(hreq, "ceQR", "Database error"); return -1; } @@ -1927,7 +1915,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. @@ -1942,11 +1930,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", hreq->out_body, 0); return 0; } @@ -1978,7 +1966,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); @@ -1986,7 +1974,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } } @@ -2000,16 +1988,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"); - dacp_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:"))) @@ -2018,9 +2006,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)")); @@ -2034,7 +2022,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } @@ -2046,7 +2034,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } @@ -2079,12 +2067,12 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - dacp_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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2106,7 +2094,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); @@ -2114,7 +2102,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"); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } @@ -2123,7 +2111,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"); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } @@ -2132,7 +2120,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2150,7 +2138,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); @@ -2158,7 +2146,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"); - dacp_send_error(hreq->req, "cacr", "Invalid request"); + dacp_send_error(hreq, "cacr", "Invalid request"); return -1; } @@ -2168,7 +2156,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2226,12 +2214,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"); - dacp_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq, "cmst", "Invalid request"); return -1; } @@ -2249,7 +2237,7 @@ dacp_reply_playqueueedit(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - dacp_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq, "cmst", "Invalid request"); return -1; } } @@ -2258,7 +2246,6 @@ static int dacp_reply_playstatusupdate(struct httpd_request *hreq) { struct dacp_update_request *ur; - struct evhttp_connection *evcon; const char *param; int reqd_rev; int ret; @@ -2267,12 +2254,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"); - dacp_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq, "cmst", "Invalid request"); return -1; } @@ -2281,7 +2268,7 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Parameter revision-number not an integer\n"); - dacp_send_error(hreq->req, "cmst", "Invalid request"); + dacp_send_error(hreq, "cmst", "Invalid request"); return -1; } @@ -2290,11 +2277,11 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) // to use when he calls again. if (reqd_rev != current_rev) { - ret = make_playstatusupdate(hreq->reply); + ret = make_playstatusupdate(hreq->out_body); if (ret < 0) - httpd_send_error(hreq->req, 500, "Internal Server Error"); + httpd_send_error(hreq, 500, "Internal Server Error"); else - httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); return ret; } @@ -2305,11 +2292,11 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Out of memory for update request\n"); - dacp_send_error(hreq->req, "cmst", "Out of memory"); + dacp_send_error(hreq, "cmst", "Out of memory"); return -1; } - ur->req = hreq->req; + ur->hreq = hreq; ur->next = update_requests; update_requests = ur; @@ -2317,9 +2304,7 @@ dacp_reply_playstatusupdate(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); + httpd_request_closecb_set(hreq, update_fail_cb, ur); return 0; } @@ -2328,7 +2313,6 @@ static int dacp_reply_nowplayingartwork(struct httpd_request *hreq) { char clen[32]; - struct evkeyvalq *headers; const char *param; char *ctype; size_t len; @@ -2341,7 +2325,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"); @@ -2355,7 +2339,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"); @@ -2373,8 +2357,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) { @@ -2388,26 +2372,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", hreq->out_body, 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; } @@ -2429,12 +2412,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"); - dacp_send_error(hreq->req, "cmgt", "Invalid request"); + dacp_send_error(hreq, "cmgt", "Invalid request"); return -1; } @@ -2443,7 +2426,7 @@ dacp_reply_getproperty(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not duplicate properties parameter; out of memory\n"); - dacp_send_error(hreq->req, "cmgt", "Out of memory"); + dacp_send_error(hreq, "cmgt", "Out of memory"); return -1; } @@ -2452,7 +2435,7 @@ dacp_reply_getproperty(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for properties list\n"); - dacp_send_error(hreq->req, "cmgt", "Out of memory"); + dacp_send_error(hreq, "cmgt", "Out of memory"); goto out_free_propstr; } @@ -2465,7 +2448,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); - dacp_send_error(hreq->req, "cmgt", "Server error"); + dacp_send_error(hreq, "cmgt", "Server error"); goto out_free_proplist; } } @@ -2493,14 +2476,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", hreq->out_body, 0); return 0; @@ -2513,11 +2496,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); @@ -2536,24 +2535,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2574,14 +2559,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", hreq->out_body, 0); return 0; } @@ -2600,12 +2585,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; } @@ -2663,15 +2648,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2685,19 +2670,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2711,19 +2696,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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2737,7 +2722,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; } @@ -2745,12 +2730,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, 500, "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", hreq->out_body, HTTPD_SEND_NO_GZIP); return 0; } @@ -2865,28 +2850,19 @@ static struct httpd_uri_map dacp_handlers[] = static void dacp_request(struct httpd_request *hreq) { - struct evkeyvalq *headers; - - DPRINTF(E_DBG, L_DACP, "DACP request: '%s'\n", hreq->uri); - if (!hreq->handler) { DPRINTF(E_LOG, L_DACP, "Unrecognized path in DACP request: '%s'\n", hreq->uri); - httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request"); return; } - headers = evhttp_request_get_output_headers(hreq->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); } // Forward @@ -2894,7 +2870,7 @@ static void dacp_deinit(void); static int -dacp_init(void) +dacp_init(struct event_base *evbase) { current_rev = 2; @@ -2918,7 +2894,7 @@ dacp_init(void) goto error; } - CHECK_NULL(L_DACP, updateev = event_new(evbase_httpd, update_efd, EV_READ, playstatusupdate_cb, NULL)); + CHECK_NULL(L_DACP, updateev = event_new(evbase, update_efd, EV_READ, playstatusupdate_cb, NULL)); #else # ifdef HAVE_PIPE2 int ret = pipe2(update_pipe, O_CLOEXEC); @@ -2931,12 +2907,12 @@ dacp_init(void) goto error; } - CHECK_NULL(L_DACP, updateev = event_new(evbase_httpd, update_pipe[0], EV_READ, playstatusupdate_cb, NULL)); + CHECK_NULL(L_DACP, updateev = event_new(evbase, update_pipe[0], EV_READ, playstatusupdate_cb, NULL)); #endif /* HAVE_EVENTFD */ event_add(updateev, NULL); - CHECK_NULL(L_DACP, seek_timer = evtimer_new(evbase_httpd, seek_timer_cb, NULL)); + CHECK_NULL(L_DACP, seek_timer = evtimer_new(evbase, seek_timer_cb, NULL)); listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME | LISTENER_QUEUE); @@ -2951,18 +2927,15 @@ static void dacp_deinit(void) { struct dacp_update_request *ur; - struct evhttp_connection *evcon; + httpd_connection *conn; 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); - } + httpd_request_closecb_set(ur->hreq, NULL, NULL); + conn = httpd_request_connection_get(ur->hreq); + httpd_connection_free(conn); // TODO necessary? free(ur); } @@ -2987,6 +2960,7 @@ struct httpd_module httpd_dacp = { .name = "DACP", .type = MODULE_DACP, + .logdomain = L_DACP, .subpaths = { "/ctrl-int/", NULL }, .fullpaths = { "/ctrl-int", NULL }, .handlers = dacp_handlers, diff --git a/src/httpd_internal.h b/src/httpd_internal.h index e9b62483..661bf976 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -4,67 +4,97 @@ #include #include -#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; + +#ifdef HAVE_LIBEVHTP +struct evhtp_s; +struct evhtp_connection_s; +struct evhtp_request_s; +struct evhtp_kvs_s; +struct httpd_uri_parsed; +struct httpd_backend_data; + +typedef struct evhtp_s httpd_server; +typedef struct evhtp_connection_s httpd_connection; +typedef struct evhtp_request_s httpd_backend; +typedef struct evhtp_kvs_s httpd_headers; +typedef struct evhtp_kvs_s httpd_query; +typedef struct httpd_uri_parsed httpd_uri_parsed; +typedef struct httpd_backend_data httpd_backend_data; + +#else +struct evhttp; +struct evhttp_connection; +struct evhttp_request; +struct evkeyvalq; +struct httpd_uri_parsed; + +typedef struct evhttp 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 void httpd_backend_data; // Not used for evhttp +#endif + +typedef char *httpd_uri_path_parts[31]; +typedef void (*httpd_general_cb)(httpd_backend *backend, void *arg); +typedef void (*httpd_connection_closecb)(httpd_connection *conn, 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, +}; 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; - // Shortcut to &uri_parsed->uri - const char *uri; - // 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) - 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); -}; - /*---------------------------------- MODULES ---------------------------------*/ @@ -85,6 +115,7 @@ 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]; @@ -93,9 +124,9 @@ struct httpd_module // Pointer to the module's handler definitions struct httpd_uri_map *handlers; - int (*init)(void); + int (*init)(struct event_base *); void (*deinit)(void); - void (*request)(struct httpd_request *hreq); + void (*request)(struct httpd_request *); }; /* @@ -103,45 +134,87 @@ struct httpd_module */ struct httpd_uri_map { - int method; + enum httpd_methods method; char *regexp; int (*handler)(struct httpd_request *hreq); void *preg; }; -/* - * Helper to free the parsed uri struct - */ -void -httpd_uri_free(struct httpd_uri_parsed *parsed); +/*------------------------------- HTTPD STRUCTS ------------------------------*/ /* - * Parse an URI into the struct + * 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_uri_parsed * -httpd_uri_parse(const char *uri); +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 + // e.g. peer address string for libevhtp + 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); + // A pointer to extra data that the module handling the request might need + void *extra_data; +}; + + +/*------------------------------ HTTPD FUNCTIONS -----------------------------*/ void -httpd_stream_file(struct evhttp_request *req, int id); +httpd_stream_file(struct httpd_request *hreq, int id); -/* - * Parse a request into the httpd_request struct. Nothing is copied, so 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. - */ -int -httpd_request_parse(struct httpd_request *hreq, struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map); +void +httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent); + +void +httpd_request_unset(struct httpd_request *hreq); 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); bool -httpd_request_etag_matches(struct evhttp_request *req, const char *etag); +httpd_request_etag_matches(struct httpd_request *hreq, const char *etag); void -httpd_response_not_cachable(struct evhttp_request *req); +httpd_response_not_cachable(struct httpd_request *hreq); /* * This wrapper around evhttp_send_reply should be used whenever a request may @@ -149,7 +222,7 @@ httpd_response_not_cachable(struct evhttp_request *req); * may direct it not to. It will set CORS headers as appropriate. Should be * thread safe. * - * @in req The evhttp request struct + * @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 @@ -157,7 +230,16 @@ httpd_response_not_cachable(struct evhttp_request *req); * @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); +httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struct evbuffer *evbuf, 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, struct evbuffer *evbuf, 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 @@ -165,24 +247,138 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc * which is not possible with evhttp_send_error, because it clears the headers. * Should be thread safe. * - * @in req The evhttp request struct + * @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 evhttp_request *req, int error, const char *reason); +httpd_send_error(struct httpd_request *hreq, int error, const char *reason); /* * Redirects to the given path */ void -httpd_redirect_to(struct evhttp_request *req, const char *path); +httpd_redirect_to(struct httpd_request *hreq, const char *path); bool -httpd_admin_check_auth(struct evhttp_request *req); +httpd_admin_check_auth(struct httpd_request *hreq); 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); + + +/*-------------------------- 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_connection_free(httpd_connection *conn); + +httpd_connection * +httpd_request_connection_get(struct httpd_request *hreq); + +void +httpd_request_backend_free(struct httpd_request *hreq); + +int +httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg); + +struct event_base * +httpd_request_evbase_get(struct httpd_request *hreq); + +void +httpd_server_free(httpd_server *server); + +httpd_server * +httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_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_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf); + +void +httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason); + +void +httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg); + +void +httpd_backend_reply_end_send(httpd_backend *backend); + + +/*---------- Only called by httpd.c to populate struct httpd_request ---------*/ + +httpd_backend_data * +httpd_backend_data_create(httpd_backend *backend); + +void +httpd_backend_data_free(httpd_backend_data *backend_data); + +httpd_connection * +httpd_backend_connection_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); + +void +httpd_backend_preprocess(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 4ff4456e..58a5a2f8 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -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 } }; @@ -4719,13 +4704,9 @@ static struct httpd_uri_map adm_handlers[] = static void jsonapi_request(struct httpd_request *hreq) { - ; - struct evkeyvalq *headers; int status_code; - DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", hreq->uri); - - if (!httpd_admin_check_auth(hreq->req)) + if (!httpd_admin_check_auth(hreq)) { return; } @@ -4733,12 +4714,10 @@ jsonapi_request(struct httpd_request *hreq) if (!hreq->handler) { DPRINTF(E_LOG, L_WEB, "Unrecognized JSON API request: '%s'\n", hreq->uri); - httpd_send_error(hreq->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); if (status_code >= 400) @@ -4747,39 +4726,36 @@ jsonapi_request(struct httpd_request *hreq) switch (status_code) { case HTTP_OK: /* 200 OK */ - headers = evhttp_request_get_output_headers(hreq->req); - evhttp_add_header(headers, "Content-Type", "application/json"); - httpd_send_reply(hreq->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", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case HTTP_NOCONTENT: /* 204 No Content */ - httpd_send_reply(hreq->req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, status_code, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case HTTP_NOTMODIFIED: /* 304 Not Modified */ - httpd_send_reply(hreq->req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); break; case HTTP_BADREQUEST: /* 400 Bad Request */ - httpd_send_error(hreq->req, status_code, "Bad Request"); + httpd_send_error(hreq, status_code, "Bad Request"); break; case 403: - httpd_send_error(hreq->req, status_code, "Forbidden"); + httpd_send_error(hreq, status_code, "Forbidden"); break; case HTTP_NOTFOUND: /* 404 Not Found */ - httpd_send_error(hreq->req, status_code, "Not Found"); + httpd_send_error(hreq, status_code, "Not Found"); break; case HTTP_SERVUNAVAIL: /* 503 */ - httpd_send_error(hreq->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(hreq->req, HTTP_INTERNAL, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); break; } - - evbuffer_free(hreq->reply); } static int -jsonapi_init(void) +jsonapi_init(struct event_base *evbase) { char *temp_path; @@ -4819,6 +4795,7 @@ struct httpd_module httpd_jsonapi = { .name = "JSON API", .type = MODULE_JSONAPI, + .logdomain = L_WEB, .subpaths = { "/api/", NULL }, .handlers = adm_handlers, .init = jsonapi_init, diff --git a/src/httpd_libevhtp.c b/src/httpd_libevhtp.c new file mode 100644 index 00000000..b71c0c49 --- /dev/null +++ b/src/httpd_libevhtp.c @@ -0,0 +1,463 @@ +#include + +#include + +#include "misc.h" +#include "httpd_internal.h" + + +#include "logger.h" + +struct httpd_backend_data +{ + char peer_address[32]; + uint16_t peer_port; + httpd_connection_closecb closecb; + void *closecb_arg; + char *uri; +}; + +struct httpd_uri_parsed +{ + evhtp_uri_t *ev_uri; + bool ev_uri_is_standalone; // true if ev_uri was allocated without a request, but via _fromuri + unsigned char *path_parts_buffer; // Allocated to hold the path parts in one buffer + httpd_uri_path_parts path_parts; +}; + + +const char * +httpd_query_value_find(httpd_query *query, const char *key) +{ + return evhtp_kv_find(query, key); +} + +void +httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg) +{ + evhtp_kv_t *param; + + TAILQ_FOREACH(param, query, next) + { + cb(param->key, param->val, arg); + } +} + +void +httpd_query_clear(httpd_query *query) +{ + evhtp_kv_t *param; + + TAILQ_FOREACH(param, query, next) + { + evhtp_kv_rm_and_free(query, param); + } +} + +const char * +httpd_header_find(httpd_headers *headers, const char *key) +{ + return evhtp_header_find(headers, key); +} + +void +httpd_header_remove(httpd_headers *headers, const char *key) +{ + evhtp_header_rm_and_free(headers, evhtp_headers_find_header(headers, key)); +} + +void +httpd_header_add(httpd_headers *headers, const char *key, const char *val) +{ + evhtp_header_t *header = evhtp_header_new(key, val, 1, 1); // 1, 1 = Copy key/val + evhtp_headers_add_header(headers, header); +} + +void +httpd_headers_clear(httpd_headers *headers) +{ + evhtp_kv_t *param; + + TAILQ_FOREACH(param, headers, next) + { + evhtp_kv_rm_and_free(headers, param); + } +} + +void +httpd_connection_free(httpd_connection *conn) +{ + if (!conn) + return; + + evhtp_connection_free(conn); +} + +httpd_connection * +httpd_request_connection_get(struct httpd_request *hreq) +{ + return evhtp_request_get_connection(hreq->backend); +} + +void +httpd_request_backend_free(struct httpd_request *hreq) +{ + evhtp_request_free(hreq->backend); +} + +static short unsigned +closecb_wrapper(httpd_connection *conn, void *arg) +{ + httpd_backend_data *backend_data = arg; + backend_data->closecb(conn, backend_data->closecb_arg); + return 0; +} + +int +httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg) +{ + httpd_connection *conn; + + hreq->backend_data->closecb = cb; + hreq->backend_data->closecb_arg = arg; + + conn = httpd_request_connection_get(hreq); + if (conn) + return -1; + + if (!cb) + return evhtp_connection_unset_hook(conn, evhtp_hook_on_connection_fini); + + return evhtp_connection_set_hook(conn, evhtp_hook_on_connection_fini, closecb_wrapper, hreq->backend_data); +} + +struct event_base * +httpd_request_evbase_get(struct httpd_request *hreq) +{ + httpd_connection *conn = httpd_request_connection_get(hreq); + if (conn) + return NULL; + + return conn->evbase; +} + +void +httpd_server_free(httpd_server *server) +{ + if (!server) + return; + + evhtp_free(server); +} + +httpd_server * +httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg) +{ + evhtp_t *server; + int fd; + + server = evhtp_new(evbase, NULL); + if (!server) + goto error; + + fd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd"); + if (fd < 0) + goto error; + + if (evhtp_accept_socket(server, fd, -1) != 0) + goto error; + + evhtp_set_gencb(server, cb, arg); + + return server; + + error: + httpd_server_free(server); + return NULL; +} + +void +httpd_server_allow_origin_set(httpd_server *server, bool allow) +{ +} + +httpd_backend_data * +httpd_backend_data_create(httpd_backend *backend) +{ + httpd_backend_data *backend_data; + + backend_data = calloc(1, sizeof(httpd_backend_data)); + if (!backend_data) + return NULL; + + return backend_data; +} + +void +httpd_backend_data_free(httpd_backend_data *backend_data) +{ + free(backend_data->uri); + free(backend_data); +} + +void +httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf) +{ + if (evbuf) + evbuffer_add_buffer(backend->buffer_out, evbuf); + + evhtp_send_reply(backend, code); +} + +void +httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason) +{ + evhtp_send_reply_chunk_start(backend, code); +} + +void +httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) +{ + // TODO +} + +void +httpd_backend_reply_end_send(httpd_backend *backend) +{ + evhtp_send_reply_chunk_end(backend); +} + +httpd_connection * +httpd_backend_connection_get(httpd_backend *backend) +{ + return evhtp_request_get_connection(backend); +} + +const char * +httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data) +{ + evhtp_uri_t *uri = backend->uri; + if (!uri || !uri->path) + return NULL; + + free(backend_data->uri); + if (backend->uri->query_raw) + backend_data->uri = safe_asprintf("%s?%s", uri->path->full, backend->uri->query_raw); + else + backend_data->uri = safe_asprintf("%s", uri->path->full); + + return (const char *)backend_data->uri; +} + +httpd_headers * +httpd_backend_input_headers_get(httpd_backend *backend) +{ + return backend->headers_in; +} + +httpd_headers * +httpd_backend_output_headers_get(httpd_backend *backend) +{ + return backend->headers_out; +} + +struct evbuffer * +httpd_backend_input_buffer_get(httpd_backend *backend) +{ + return backend->buffer_in; +} + +int +httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data) +{ + httpd_connection *conn; + union net_sockaddr naddr; + socklen_t sa_len = sizeof(naddr); + + *addr = NULL; + *port = 0; + + conn = evhtp_request_get_connection(backend); + if (!conn) + return -1; + + // We cannot use conn->saddr as we don't have the size, so it won't work for ipv6 + getpeername(conn->sock, &naddr.sa, &sa_len); + + net_address_get(backend_data->peer_address, sizeof(backend_data->peer_address), &naddr); + net_port_get(&backend_data->peer_port, &naddr); + + *addr = backend_data->peer_address; + *port = backend_data->peer_port; + return 0; +} + +int +httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend) +{ + htp_method cmd = evhtp_request_get_method(backend); + + switch (cmd) + { + case htp_method_GET: *method = HTTPD_METHOD_GET; break; + case htp_method_POST: *method = HTTPD_METHOD_POST; break; + case htp_method_HEAD: *method = HTTPD_METHOD_HEAD; break; + case htp_method_PUT: *method = HTTPD_METHOD_PUT; break; + case htp_method_DELETE: *method = HTTPD_METHOD_DELETE; break; + case htp_method_OPTIONS: *method = HTTPD_METHOD_OPTIONS; break; + case htp_method_TRACE: *method = HTTPD_METHOD_TRACE; break; + case htp_method_CONNECT: *method = HTTPD_METHOD_CONNECT; break; + case htp_method_PATCH: *method = HTTPD_METHOD_PATCH; break; + default: *method = HTTPD_METHOD_GET; return -1; + } + + return 0; +} + +void +httpd_backend_preprocess(httpd_backend *backend) +{ + // Nothing to do here +} + +static int +query_decode(evhtp_kvs_t **query) +{ + evhtp_kvs_t *query_decoded; + evhtp_kv_t *encoded; + evhtp_kv_t *decoded; + char buf[2048]; + unsigned char *out; + size_t val_size; + + query_decoded = evhtp_kvs_new(); + if (!query_decoded) + return -1; + + TAILQ_FOREACH(encoded, *query, next) + { + // Must include zero terminator in length or output won't be terminated + // (not very clear from evhtp docs) + val_size = strlen(encoded->val) + 1; + if (val_size > sizeof(buf)) + continue; + + // Isn't done by evhtp_unescape_string :-( + safe_snreplace(encoded->val, val_size, "+", " "); + + out = (unsigned char *)buf; + evhtp_unescape_string(&out, (unsigned char *)encoded->val, val_size); + decoded = evhtp_kv_new(encoded->key, buf, 1, 1); // 1, 1 = Copy key/val + evhtp_kvs_add_kv(query_decoded, decoded); + } + + evhtp_kvs_free(*query); + *query = query_decoded; + return 0; +} + +httpd_uri_parsed * +httpd_uri_parsed_create(httpd_backend *backend) +{ + httpd_uri_parsed *parsed = NULL; + char *path = NULL; + size_t path_len; + char *path_part; + off_t path_part_offset; + char *ptr; + unsigned char *unescaped_part; + int i; + + if (!backend->uri->path->path) // Not sure if this can happen + goto error; + + path_len = strlen(backend->uri->path->path); + if (path_len == 0) + goto error; + + path = strdup(backend->uri->path->path); + if (!path) + goto error; + + parsed = calloc(1, sizeof(struct httpd_uri_parsed)); + if (!parsed) + goto error; + + // Pointers of parsed->path_parts will point into this buffer, so it will hold + // the uri decoded path parts separated by zeroes + parsed->path_parts_buffer = calloc(1, path_len + 1); + if (!parsed->path_parts_buffer) + goto error; + + parsed->ev_uri = backend->uri; + + path_part = strtok_r(path, "/", &ptr); + path_part_offset = path_part - path; + for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++) + { + // libevhtp's evhtp_unescape_string() is wonky (and feels unsafe...), for + // some reason it wants a double pointer to a user allocated buffer. + unescaped_part = parsed->path_parts_buffer + (path_part - path) - path_part_offset; + parsed->path_parts[i] = (char *)unescaped_part; + + evhtp_unescape_string(&unescaped_part, (unsigned char *)path_part, strlen(path_part)); + 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; + + // uri->query isn't uri decoded, so we replace it with one that is + if (backend->uri->query) + query_decode(&backend->uri->query); + + free(path); + return parsed; + + error: + httpd_uri_parsed_free(parsed); + free(path); + return NULL; +} + +httpd_uri_parsed * +httpd_uri_parsed_create_fromuri(const char *uri) +{ +// TODO + return NULL; +} + +void +httpd_uri_parsed_free(httpd_uri_parsed *parsed) +{ + if (!parsed) + return; +// TODO +// if (parsed->ev_uri_is_standalone) +// free ev_uri; + + free(parsed->path_parts_buffer); + free(parsed); +} + +httpd_query * +httpd_uri_query_get(httpd_uri_parsed *parsed) +{ + return parsed->ev_uri->query; +} + +const char * +httpd_uri_path_get(httpd_uri_parsed *parsed) +{ + if (!parsed->ev_uri->path) + return NULL; + + return parsed->ev_uri->path->full; +} + +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_libevhttp.c b/src/httpd_libevhttp.c new file mode 100644 index 00000000..c948d7f0 --- /dev/null +++ b/src/httpd_libevhttp.c @@ -0,0 +1,350 @@ +#include +#include +#include + +#include +#include +#include + +#include "misc.h" // For net_evhttp_bind +#include "httpd_internal.h" + +struct httpd_uri_parsed +{ + struct evhttp_uri *ev_uri; + struct evkeyvalq query; + char *path; + httpd_uri_path_parts path_parts; +}; + + +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_connection_free(httpd_connection *conn) +{ + if (!conn) + return; + + evhttp_connection_free(conn); +} + +httpd_connection * +httpd_request_connection_get(struct httpd_request *hreq) +{ + return httpd_backend_connection_get(hreq->backend); +} + +void +httpd_request_backend_free(struct httpd_request *hreq) +{ + evhttp_request_free(hreq->backend); +} + +int +httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg) +{ + httpd_connection *conn = httpd_request_connection_get(hreq); + if (!conn) + return -1; + + evhttp_connection_set_closecb(conn, cb, arg); + return 0; +} + +struct event_base * +httpd_request_evbase_get(struct httpd_request *hreq) +{ + httpd_connection *conn = httpd_request_connection_get(hreq); + if (conn) + return NULL; + + return evhttp_connection_get_base(conn); +} + +void +httpd_server_free(httpd_server *server) +{ + if (!server) + return; + + evhttp_free(server); +} + +httpd_server * +httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg) +{ + int ret; + struct evhttp *server = evhttp_new(evbase); + + if (!server) + goto error; + + ret = net_evhttp_bind(server, port, "httpd"); + if (ret < 0) + goto error; + + evhttp_set_gencb(server, cb, arg); + + 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_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS); +} + +void +httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf) +{ + evhttp_send_reply(backend, code, reason, evbuf); +} + +void +httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason) +{ + evhttp_send_reply_start(backend, code, reason); +} + +void +httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) +{ + evhttp_send_reply_chunk_with_cb(backend, evbuf, cb, arg); +} + +void +httpd_backend_reply_end_send(httpd_backend *backend) +{ + evhttp_send_reply_end(backend); +} + +httpd_backend_data * +httpd_backend_data_create(httpd_backend *backend) +{ + return "dummy"; +} + +void +httpd_backend_data_free(httpd_backend_data *backend_data) +{ + // Nothing to do +} + +httpd_connection * +httpd_backend_connection_get(httpd_backend *backend) +{ + return evhttp_request_get_connection(backend); +} + +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 = httpd_backend_connection_get(backend); + if (!conn) + return -1; + + evhttp_connection_get_peer(conn, (char **)addr, port); + 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; +} + +void +httpd_backend_preprocess(httpd_backend *backend) +{ + // 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; +} + +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 ff41bf76..951aef49 100644 --- a/src/httpd_oauth.c +++ b/src/httpd_oauth.c @@ -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; } @@ -93,13 +93,11 @@ static struct httpd_uri_map oauth_handlers[] = static void oauth_request(struct httpd_request *hreq) { - DPRINTF(E_LOG, L_WEB, "OAuth request: '%s'\n", hreq->uri); - if (!hreq->handler) { DPRINTF(E_LOG, L_WEB, "Unrecognized path in OAuth request: '%s'\n", hreq->uri); - httpd_send_error(hreq->req, HTTP_NOTFOUND, NULL); + httpd_send_error(hreq, HTTP_NOTFOUND, NULL); return; } @@ -110,6 +108,7 @@ struct httpd_module httpd_oauth = { .name = "OAuth", .type = MODULE_OAUTH, + .logdomain = L_WEB, .subpaths = { "/oauth/", NULL }, .fullpaths = { "/oauth", NULL }, .handlers = oauth_handlers, diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index af10f445..edbf3e3f 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -157,10 +157,9 @@ mxml_to_evbuf(mxml_node_t *tree) } 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; @@ -191,16 +190,15 @@ rsp_send_error(struct evhttp_request *req, char *errmsg) if (!evbuf) { - 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); + httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); evbuffer_free(evbuf); } @@ -214,25 +212,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; } } @@ -243,7 +241,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); @@ -277,26 +275,24 @@ 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; evbuf = mxml_to_evbuf(reply); mxmlDelete(reply); if (!evbuf) { - 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); + httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, 0); evbuffer_free(evbuf); } @@ -317,7 +313,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); @@ -381,7 +377,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; } @@ -410,7 +406,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; } @@ -464,7 +460,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; } @@ -478,7 +474,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; } @@ -488,7 +484,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; @@ -507,10 +502,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; } @@ -522,7 +517,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) @@ -546,7 +541,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); @@ -586,10 +581,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); @@ -657,7 +650,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; } @@ -671,7 +664,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; } @@ -690,34 +683,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; } @@ -730,7 +723,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); @@ -783,7 +776,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; } @@ -797,7 +790,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; } @@ -808,14 +801,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; } @@ -867,20 +860,18 @@ rsp_request(struct httpd_request *hreq) { int ret; - DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", hreq->uri); - if (!hreq->handler) { DPRINTF(E_LOG, L_RSP, "Unrecognized path in RSP request: '%s'\n", hreq->uri); - rsp_send_error(hreq->req, "Server error"); + rsp_send_error(hreq, "Server error"); return; } ret = rsp_request_authorize(hreq); if (ret < 0) { - rsp_send_error(hreq->req, "Access denied"); + rsp_send_error(hreq, "Access denied"); free(hreq); return; } @@ -889,7 +880,7 @@ rsp_request(struct httpd_request *hreq) } static int -rsp_init(void) +rsp_init(struct event_base *evbase) { snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE); @@ -900,6 +891,7 @@ struct httpd_module httpd_rsp = { .name = "RSP", .type = MODULE_RSP, + .logdomain = L_RSP, .subpaths = { "/rsp/", NULL }, .handlers = rsp_handlers, .init = rsp_init, diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 09075336..85a82f41 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -41,9 +41,6 @@ #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 @@ -58,7 +55,7 @@ extern struct event_base *evbase_httpd; // Linked list of mp3 streaming requests struct streaming_session { - struct evhttp_request *req; + struct httpd_request *hreq; struct streaming_session *next; bool require_icy; // Client requested icy meta @@ -102,18 +99,15 @@ static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX]; static void -streaming_close_cb(struct evhttp_connection *evcon, void *arg) +streaming_close_cb(httpd_connection *conn, 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); + DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", this->hreq->peer_address, (int)this->hreq->peer_port); pthread_mutex_lock(&streaming_sessions_lck); if (!streaming_sessions) @@ -127,7 +121,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg) prev = NULL; for (session = streaming_sessions; session; session = session->next) { - if (session->req == this->req) + if (session->hreq == this->hreq) break; prev = session; @@ -135,7 +129,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg) if (!session) { - DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", address, (int)port); + DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", this->hreq->peer_address, (int)this->hreq->peer_port); free(this); pthread_mutex_unlock(&streaming_sessions_lck); return; @@ -151,7 +145,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg) // 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); + httpd_send_reply_end(session->hreq); free(session); if (!streaming_sessions) @@ -168,13 +162,17 @@ static void streaming_end(void) { struct streaming_session *session; +<<<<<<< HEAD struct evhttp_connection *evcon; const char *address; ev_uint16_t port; +======= +>>>>>>> [httpd] Remove all traces of evhttp from httpd modules pthread_mutex_lock(&streaming_sessions_lck); for (session = streaming_sessions; streaming_sessions; session = streaming_sessions) { +<<<<<<< HEAD evcon = evhttp_request_get_connection(session->req); if (evcon) { @@ -183,6 +181,16 @@ streaming_end(void) DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", address, (int)port); } evhttp_send_reply_end(session->req); +======= + DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); + + httpd_request_closecb_set(session->hreq, NULL, NULL); +<<<<<<< HEAD + httpd_reply_end_send(session->hreq); +>>>>>>> [httpd] Remove all traces of evhttp from httpd modules +======= + httpd_send_reply_end(session->hreq); +>>>>>>> [httpd] Changes to httpd_send_reply_chunk et al streaming_sessions = session->next; free(session); @@ -450,7 +458,7 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg) free(splice_buf); splice_buf = NULL; - evhttp_send_reply_chunk(session->req, evbuf); + httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL); if (session->next == NULL) { @@ -465,11 +473,11 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg) { buf = evbuffer_pullup(streaming_encoded_data, -1); evbuffer_add(evbuf, buf, len); - evhttp_send_reply_chunk(session->req, evbuf); + httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL); } else { - evhttp_send_reply_chunk(session->req, streaming_encoded_data); + httpd_send_reply_chunk(session->hreq, streaming_encoded_data, NULL, NULL); } session->bytes_sent += len; } @@ -547,12 +555,8 @@ static void streaming_request(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 *param; bool require_icy = false; char buf[9]; @@ -561,45 +565,42 @@ streaming_request(struct httpd_request *hreq) { DPRINTF(E_LOG, L_STREAMING, "Got MP3 streaming request, but cannot encode to MP3\n"); - evhttp_send_error(hreq->req, HTTP_NOTFOUND, "Not Found"); + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); return; } - evcon = evhttp_request_get_connection(hreq->req); - evhttp_connection_get_peer(evcon, &address, &port); - param = evhttp_find_header( evhttp_request_get_input_headers(hreq->req), "Icy-MetaData"); + param = httpd_header_find(hreq->in_headers, "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); + DPRINTF(E_INFO, L_STREAMING, "Beginning mp3 streaming (with icy=%d, icy_metaint=%d) to %s:%d\n", require_icy, streaming_icy_metaint, hreq->peer_address, (int)hreq->peer_port); lib = cfg_getsec(cfg, "library"); name = cfg_getstr(lib, "name"); - output_headers = evhttp_request_get_output_headers(hreq->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"); + 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"); if (require_icy) { ++streaming_icy_clients; - evhttp_add_header(output_headers, "icy-name", name); + httpd_header_add(hreq->out_headers, "icy-name", name); snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint); - evhttp_add_header(output_headers, "icy-metaint", buf); + httpd_header_add(hreq->out_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"); + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", "*"); + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - evhttp_send_reply_start(hreq->req, HTTP_OK, "OK"); + httpd_send_reply_start(hreq, HTTP_OK, "OK"); session = calloc(1, sizeof(struct streaming_session)); if (!session) { DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n"); - evhttp_send_error(hreq->req, HTTP_SERVUNAVAIL, "Internal Server Error"); + httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); return; } @@ -611,7 +612,7 @@ streaming_request(struct httpd_request *hreq) event_add(metaev, NULL); } - session->req = hreq->req; + session->hreq = hreq; session->next = streaming_sessions; session->require_icy = require_icy; session->bytes_sent = 0; @@ -619,11 +620,11 @@ streaming_request(struct httpd_request *hreq) pthread_mutex_unlock(&streaming_sessions_lck); - evhttp_connection_set_closecb(evcon, streaming_close_cb, session); + httpd_request_closecb_set(hreq, streaming_close_cb, session); } static int -streaming_init(void) +streaming_init(struct event_base *evbase) { int ret; cfg_t *cfgsec; @@ -713,8 +714,8 @@ streaming_init(void) // 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)); + CHECK_NULL(L_STREAMING, streamingev = event_new(evbase, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL)); + CHECK_NULL(L_STREAMING, metaev = event_new(evbase, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL)); streaming_icy_clients = 0; @@ -755,6 +756,7 @@ struct httpd_module httpd_streaming = { .name = "Streaming", .type = MODULE_STREAMING, + .logdomain = L_STREAMING, .fullpaths = { "/stream.mp3", NULL }, .handlers = streaming_handlers, .init = streaming_init, diff --git a/src/misc.c b/src/misc.c index 3acd732b..625b09a2 100644 --- a/src/misc.c +++ b/src/misc.c @@ -52,6 +52,8 @@ #include #include // getifaddrs +#include // evhttp_bind + #include #include diff --git a/src/misc.h b/src/misc.h index 60455415..97f5da9b 100644 --- a/src/misc.h +++ b/src/misc.h @@ -14,7 +14,6 @@ #include #include -#include #ifndef SOCK_NONBLOCK #include @@ -54,6 +53,9 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser int net_bind(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); From 68d66c32291916ebc88d3294bcb241fe48f20575 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 9 Jan 2023 22:02:53 +0100 Subject: [PATCH 10/21] [httpd] Drop libevhtp backend Despite that it seemingly supports multithreading it picks threads that are busy, and not sure how that can be prevented. Also libevhtp is currently unmaintained. --- configure.ac | 5 - src/Makefile.am | 8 +- src/httpd_internal.h | 20 +- src/httpd_libevhtp.c | 463 ------------------------------------------- 4 files changed, 3 insertions(+), 493 deletions(-) delete mode 100644 src/httpd_libevhtp.c diff --git a/configure.ac b/configure.ac index d3f4eed6..16822734 100644 --- a/configure.ac +++ b/configure.ac @@ -268,11 +268,6 @@ OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevent_pthreads support], [libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads], [evthread_use_pthreads], [event2/thread.h]) -dnl Build with libevhtp -OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevhtp support], [libevhtp], [LIBEVHTP], - [evhtp]) -AM_CONDITIONAL([COND_LIBEVHTP], [[test "x$with_libevhtp" = "xyes"]]) - 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/src/Makefile.am b/src/Makefile.am index d468d831..a23c032f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,12 +46,6 @@ if COND_LIBWEBSOCKETS LIBWEBSOCKETS_SRC=websocket.c websocket.h endif -if COND_LIBEVHTP -HTTPDBACKEND_SRC=httpd_libevhtp.c -else -HTTPDBACKEND_SRC=httpd_libevhttp.c -endif - GPERF_FILES = \ daap_query.gperf \ dacp_prop.gperf \ @@ -98,7 +92,7 @@ owntone_SOURCES = main.c \ library.c library.h \ $(MDNS_SRC) mdns.h \ remote_pairing.c remote_pairing.h \ - $(HTTPDBACKEND_SRC) \ + httpd_libevhttp.c \ httpd.c httpd.h httpd_internal.h \ httpd_rsp.c \ httpd_daap.c httpd_daap.h \ diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 661bf976..bab5e07f 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -39,23 +39,8 @@ struct httpd_request; -#ifdef HAVE_LIBEVHTP -struct evhtp_s; -struct evhtp_connection_s; -struct evhtp_request_s; -struct evhtp_kvs_s; -struct httpd_uri_parsed; -struct httpd_backend_data; - -typedef struct evhtp_s httpd_server; -typedef struct evhtp_connection_s httpd_connection; -typedef struct evhtp_request_s httpd_backend; -typedef struct evhtp_kvs_s httpd_headers; -typedef struct evhtp_kvs_s httpd_query; -typedef struct httpd_uri_parsed httpd_uri_parsed; -typedef struct httpd_backend_data httpd_backend_data; - -#else +// Declaring here instead of including event2/http.h makes it easier to support +// other backends than evhttp in the future, e.g. libevhtp struct evhttp; struct evhttp_connection; struct evhttp_request; @@ -69,7 +54,6 @@ typedef struct evkeyvalq httpd_headers; typedef struct evkeyvalq httpd_query; typedef struct httpd_uri_parsed httpd_uri_parsed; typedef void httpd_backend_data; // Not used for evhttp -#endif typedef char *httpd_uri_path_parts[31]; typedef void (*httpd_general_cb)(httpd_backend *backend, void *arg); diff --git a/src/httpd_libevhtp.c b/src/httpd_libevhtp.c deleted file mode 100644 index b71c0c49..00000000 --- a/src/httpd_libevhtp.c +++ /dev/null @@ -1,463 +0,0 @@ -#include - -#include - -#include "misc.h" -#include "httpd_internal.h" - - -#include "logger.h" - -struct httpd_backend_data -{ - char peer_address[32]; - uint16_t peer_port; - httpd_connection_closecb closecb; - void *closecb_arg; - char *uri; -}; - -struct httpd_uri_parsed -{ - evhtp_uri_t *ev_uri; - bool ev_uri_is_standalone; // true if ev_uri was allocated without a request, but via _fromuri - unsigned char *path_parts_buffer; // Allocated to hold the path parts in one buffer - httpd_uri_path_parts path_parts; -}; - - -const char * -httpd_query_value_find(httpd_query *query, const char *key) -{ - return evhtp_kv_find(query, key); -} - -void -httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg) -{ - evhtp_kv_t *param; - - TAILQ_FOREACH(param, query, next) - { - cb(param->key, param->val, arg); - } -} - -void -httpd_query_clear(httpd_query *query) -{ - evhtp_kv_t *param; - - TAILQ_FOREACH(param, query, next) - { - evhtp_kv_rm_and_free(query, param); - } -} - -const char * -httpd_header_find(httpd_headers *headers, const char *key) -{ - return evhtp_header_find(headers, key); -} - -void -httpd_header_remove(httpd_headers *headers, const char *key) -{ - evhtp_header_rm_and_free(headers, evhtp_headers_find_header(headers, key)); -} - -void -httpd_header_add(httpd_headers *headers, const char *key, const char *val) -{ - evhtp_header_t *header = evhtp_header_new(key, val, 1, 1); // 1, 1 = Copy key/val - evhtp_headers_add_header(headers, header); -} - -void -httpd_headers_clear(httpd_headers *headers) -{ - evhtp_kv_t *param; - - TAILQ_FOREACH(param, headers, next) - { - evhtp_kv_rm_and_free(headers, param); - } -} - -void -httpd_connection_free(httpd_connection *conn) -{ - if (!conn) - return; - - evhtp_connection_free(conn); -} - -httpd_connection * -httpd_request_connection_get(struct httpd_request *hreq) -{ - return evhtp_request_get_connection(hreq->backend); -} - -void -httpd_request_backend_free(struct httpd_request *hreq) -{ - evhtp_request_free(hreq->backend); -} - -static short unsigned -closecb_wrapper(httpd_connection *conn, void *arg) -{ - httpd_backend_data *backend_data = arg; - backend_data->closecb(conn, backend_data->closecb_arg); - return 0; -} - -int -httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg) -{ - httpd_connection *conn; - - hreq->backend_data->closecb = cb; - hreq->backend_data->closecb_arg = arg; - - conn = httpd_request_connection_get(hreq); - if (conn) - return -1; - - if (!cb) - return evhtp_connection_unset_hook(conn, evhtp_hook_on_connection_fini); - - return evhtp_connection_set_hook(conn, evhtp_hook_on_connection_fini, closecb_wrapper, hreq->backend_data); -} - -struct event_base * -httpd_request_evbase_get(struct httpd_request *hreq) -{ - httpd_connection *conn = httpd_request_connection_get(hreq); - if (conn) - return NULL; - - return conn->evbase; -} - -void -httpd_server_free(httpd_server *server) -{ - if (!server) - return; - - evhtp_free(server); -} - -httpd_server * -httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg) -{ - evhtp_t *server; - int fd; - - server = evhtp_new(evbase, NULL); - if (!server) - goto error; - - fd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd"); - if (fd < 0) - goto error; - - if (evhtp_accept_socket(server, fd, -1) != 0) - goto error; - - evhtp_set_gencb(server, cb, arg); - - return server; - - error: - httpd_server_free(server); - return NULL; -} - -void -httpd_server_allow_origin_set(httpd_server *server, bool allow) -{ -} - -httpd_backend_data * -httpd_backend_data_create(httpd_backend *backend) -{ - httpd_backend_data *backend_data; - - backend_data = calloc(1, sizeof(httpd_backend_data)); - if (!backend_data) - return NULL; - - return backend_data; -} - -void -httpd_backend_data_free(httpd_backend_data *backend_data) -{ - free(backend_data->uri); - free(backend_data); -} - -void -httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf) -{ - if (evbuf) - evbuffer_add_buffer(backend->buffer_out, evbuf); - - evhtp_send_reply(backend, code); -} - -void -httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason) -{ - evhtp_send_reply_chunk_start(backend, code); -} - -void -httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) -{ - // TODO -} - -void -httpd_backend_reply_end_send(httpd_backend *backend) -{ - evhtp_send_reply_chunk_end(backend); -} - -httpd_connection * -httpd_backend_connection_get(httpd_backend *backend) -{ - return evhtp_request_get_connection(backend); -} - -const char * -httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data) -{ - evhtp_uri_t *uri = backend->uri; - if (!uri || !uri->path) - return NULL; - - free(backend_data->uri); - if (backend->uri->query_raw) - backend_data->uri = safe_asprintf("%s?%s", uri->path->full, backend->uri->query_raw); - else - backend_data->uri = safe_asprintf("%s", uri->path->full); - - return (const char *)backend_data->uri; -} - -httpd_headers * -httpd_backend_input_headers_get(httpd_backend *backend) -{ - return backend->headers_in; -} - -httpd_headers * -httpd_backend_output_headers_get(httpd_backend *backend) -{ - return backend->headers_out; -} - -struct evbuffer * -httpd_backend_input_buffer_get(httpd_backend *backend) -{ - return backend->buffer_in; -} - -int -httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data) -{ - httpd_connection *conn; - union net_sockaddr naddr; - socklen_t sa_len = sizeof(naddr); - - *addr = NULL; - *port = 0; - - conn = evhtp_request_get_connection(backend); - if (!conn) - return -1; - - // We cannot use conn->saddr as we don't have the size, so it won't work for ipv6 - getpeername(conn->sock, &naddr.sa, &sa_len); - - net_address_get(backend_data->peer_address, sizeof(backend_data->peer_address), &naddr); - net_port_get(&backend_data->peer_port, &naddr); - - *addr = backend_data->peer_address; - *port = backend_data->peer_port; - return 0; -} - -int -httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend) -{ - htp_method cmd = evhtp_request_get_method(backend); - - switch (cmd) - { - case htp_method_GET: *method = HTTPD_METHOD_GET; break; - case htp_method_POST: *method = HTTPD_METHOD_POST; break; - case htp_method_HEAD: *method = HTTPD_METHOD_HEAD; break; - case htp_method_PUT: *method = HTTPD_METHOD_PUT; break; - case htp_method_DELETE: *method = HTTPD_METHOD_DELETE; break; - case htp_method_OPTIONS: *method = HTTPD_METHOD_OPTIONS; break; - case htp_method_TRACE: *method = HTTPD_METHOD_TRACE; break; - case htp_method_CONNECT: *method = HTTPD_METHOD_CONNECT; break; - case htp_method_PATCH: *method = HTTPD_METHOD_PATCH; break; - default: *method = HTTPD_METHOD_GET; return -1; - } - - return 0; -} - -void -httpd_backend_preprocess(httpd_backend *backend) -{ - // Nothing to do here -} - -static int -query_decode(evhtp_kvs_t **query) -{ - evhtp_kvs_t *query_decoded; - evhtp_kv_t *encoded; - evhtp_kv_t *decoded; - char buf[2048]; - unsigned char *out; - size_t val_size; - - query_decoded = evhtp_kvs_new(); - if (!query_decoded) - return -1; - - TAILQ_FOREACH(encoded, *query, next) - { - // Must include zero terminator in length or output won't be terminated - // (not very clear from evhtp docs) - val_size = strlen(encoded->val) + 1; - if (val_size > sizeof(buf)) - continue; - - // Isn't done by evhtp_unescape_string :-( - safe_snreplace(encoded->val, val_size, "+", " "); - - out = (unsigned char *)buf; - evhtp_unescape_string(&out, (unsigned char *)encoded->val, val_size); - decoded = evhtp_kv_new(encoded->key, buf, 1, 1); // 1, 1 = Copy key/val - evhtp_kvs_add_kv(query_decoded, decoded); - } - - evhtp_kvs_free(*query); - *query = query_decoded; - return 0; -} - -httpd_uri_parsed * -httpd_uri_parsed_create(httpd_backend *backend) -{ - httpd_uri_parsed *parsed = NULL; - char *path = NULL; - size_t path_len; - char *path_part; - off_t path_part_offset; - char *ptr; - unsigned char *unescaped_part; - int i; - - if (!backend->uri->path->path) // Not sure if this can happen - goto error; - - path_len = strlen(backend->uri->path->path); - if (path_len == 0) - goto error; - - path = strdup(backend->uri->path->path); - if (!path) - goto error; - - parsed = calloc(1, sizeof(struct httpd_uri_parsed)); - if (!parsed) - goto error; - - // Pointers of parsed->path_parts will point into this buffer, so it will hold - // the uri decoded path parts separated by zeroes - parsed->path_parts_buffer = calloc(1, path_len + 1); - if (!parsed->path_parts_buffer) - goto error; - - parsed->ev_uri = backend->uri; - - path_part = strtok_r(path, "/", &ptr); - path_part_offset = path_part - path; - for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++) - { - // libevhtp's evhtp_unescape_string() is wonky (and feels unsafe...), for - // some reason it wants a double pointer to a user allocated buffer. - unescaped_part = parsed->path_parts_buffer + (path_part - path) - path_part_offset; - parsed->path_parts[i] = (char *)unescaped_part; - - evhtp_unescape_string(&unescaped_part, (unsigned char *)path_part, strlen(path_part)); - 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; - - // uri->query isn't uri decoded, so we replace it with one that is - if (backend->uri->query) - query_decode(&backend->uri->query); - - free(path); - return parsed; - - error: - httpd_uri_parsed_free(parsed); - free(path); - return NULL; -} - -httpd_uri_parsed * -httpd_uri_parsed_create_fromuri(const char *uri) -{ -// TODO - return NULL; -} - -void -httpd_uri_parsed_free(httpd_uri_parsed *parsed) -{ - if (!parsed) - return; -// TODO -// if (parsed->ev_uri_is_standalone) -// free ev_uri; - - free(parsed->path_parts_buffer); - free(parsed); -} - -httpd_query * -httpd_uri_query_get(httpd_uri_parsed *parsed) -{ - return parsed->ev_uri->query; -} - -const char * -httpd_uri_path_get(httpd_uri_parsed *parsed) -{ - if (!parsed->ev_uri->path) - return NULL; - - return parsed->ev_uri->path->full; -} - -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)); -} From 3377faffb88f6212e527dec7ee2207c09b44f999 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 10 Jan 2023 16:22:06 +0100 Subject: [PATCH 11/21] [worker] Switch worker to use evthr threadpool --- src/worker.c | 546 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 469 insertions(+), 77 deletions(-) diff --git a/src/worker.c b/src/worker.c index 23ad95c4..236b00eb 100644 --- a/src/worker.c +++ b/src/worker.c @@ -29,16 +29,455 @@ #include #include #include +#include +#include + +#include +#include + #include #include +#include #include "db.h" #include "logger.h" #include "worker.h" -#include "commands.h" #include "misc.h" +#define THREADPOOL_NTHREADS 4 + +struct evthr_pool; + +static struct evthr_pool *worker_threadpool; + + +/* ----------------- Thread handling borrowed from libevhtp ----------------- */ + +#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 + +enum evthr_res { + EVTHR_RES_OK = 0, + EVTHR_RES_BACKLOG, + EVTHR_RES_RETRY, + EVTHR_RES_NOCB, + EVTHR_RES_FATAL +}; + +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 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); + + CHECK_ERR(L_MAIN, thread->err); + + 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; +} + +static struct event_base * +evthr_get_base(struct evthr * thr) +{ + return thr ? thr->evbase : 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; +} + +static 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); +} + +static 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)); +} + +static 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); +} + +static 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; +} + +static 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; +} + + +/* ----------------------------- CALLBACK EXECUTION ------------------------- */ +/* Worker threads */ struct worker_arg { @@ -49,19 +488,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 +500,51 @@ 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; ret = db_perthread_init(); if (ret < 0) { - DPRINTF(E_LOG, L_MAIN, "Error: DB init failed (worker thread)\n"); - pthread_exit(NULL); + DPRINTF(E_FATAL, L_MAIN, "Error: DB init failed (worker thread)\n"); + thr->err = EIO; + return; } - g_initialized = 1; - - event_base_dispatch(evbase_worker); - - if (g_initialized) - { - DPRINTF(E_LOG, L_MAIN, "Worker event loop terminated ahead of time!\n"); - g_initialized = 0; - } + thread_setname(pthread_self(), "worker"); +} +static void +exit_cb(struct evthr *thr, void *shared) +{ 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 +577,7 @@ 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); } int @@ -172,51 +585,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); } From 4736e10d11fb7946489e055dccb1badfef294e4b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 10 Jan 2023 23:36:09 +0100 Subject: [PATCH 12/21] [worker] Remove some unused includes --- src/worker.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/worker.c b/src/worker.c index 236b00eb..f665fc40 100644 --- a/src/worker.c +++ b/src/worker.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -38,7 +37,6 @@ #include #include -#include #include "db.h" #include "logger.h" From 50a319df2bdec7cf0056cbbe7230818f86e58751 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 9 Jan 2023 23:15:08 +0100 Subject: [PATCH 13/21] [httpd] Try doing request handling in worker thread --- src/httpd.c | 102 ++--------------------- src/httpd_daap.c | 25 +++--- src/httpd_internal.h | 22 ++--- src/httpd_libevhttp.c | 187 ++++++++++++++++++++++++++++++++++++++---- src/httpd_streaming.c | 21 ----- 5 files changed, 205 insertions(+), 152 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index 970b1b03..5694b997 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -291,65 +291,12 @@ modules_search(const char *path) /* --------------------------- REQUEST HELPERS ------------------------------ */ -static void -request_unset(struct httpd_request *hreq) +void +httpd_request_handler_set(struct httpd_request *hreq) { - if (hreq->out_body) - evbuffer_free(hreq->out_body); - - httpd_uri_parsed_free(hreq->uri_parsed); - httpd_backend_data_free(hreq->backend_data); -} - -static void -request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, const char *user_agent) -{ - httpd_backend_data *backend_data; struct httpd_uri_map *map; int ret; - memset(hreq, 0, sizeof(struct httpd_request)); - - // Populate hreq by getting values from the backend (or from the caller) - hreq->backend = backend; - if (backend) - { - backend_data = httpd_backend_data_create(backend); - 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); - return; - } - - // 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); - // Path with e.g. /api -> JSON module hreq->module = modules_search(hreq->path); if (!hreq->module) @@ -855,36 +802,28 @@ handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) } static void -httpd_gen_cb(httpd_backend *backend, void *arg) +request_cb(struct httpd_request *hreq, void *arg) { - struct httpd_request hreq_stack; - struct httpd_request *hreq = &hreq_stack; // Shorthand - - // This is to make modifications to e.g. evhttps's request object - httpd_backend_preprocess(backend); - - // Populates the hreq struct - request_set(hreq, backend, NULL, NULL); - // Did we get a CORS preflight request? if (handle_cors_preflight(hreq, httpd_allow_origin) == 0) { - goto out; + return; } if (!hreq->uri || !hreq->uri_parsed) { DPRINTF(E_WARN, L_HTTPD, "Invalid URI in request: '%s'\n", hreq->uri); httpd_redirect_to(hreq, "/"); - goto out; + return; } else if (!hreq->path) { DPRINTF(E_WARN, L_HTTPD, "Invalid path in request: '%s'\n", hreq->uri); httpd_redirect_to(hreq, "/"); - goto out; + return; } + httpd_request_handler_set(hreq); if (hreq->module) { DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s' (thread %ld)\n", hreq->module->name, hreq->uri, syscall(SYS_gettid)); @@ -896,26 +835,11 @@ httpd_gen_cb(httpd_backend *backend, void *arg) DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", hreq->uri); serve_file(hreq); } - - out: - request_unset(hreq); } /* ------------------------------- HTTPD API -------------------------------- */ -void -httpd_request_unset(struct httpd_request *hreq) -{ - request_unset(hreq); -} - -void -httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent) -{ - request_set(hreq, NULL, uri, user_agent); -} - /* Thread: httpd */ void httpd_stream_file(struct httpd_request *hreq, int id) @@ -1484,16 +1408,6 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw return -1; } -void -httpd_peer_get(const char **address, ev_uint16_t *port, struct evhttp_connection *evcon) -{ -#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 -} - /* Thread: main */ int httpd_init(const char *webroot) @@ -1537,7 +1451,7 @@ httpd_init(const char *webroot) event_add(exitev, NULL); httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - httpd_serv = httpd_server_new(evbase_httpd, httpd_port, httpd_gen_cb, NULL); + httpd_serv = httpd_server_new(evbase_httpd, httpd_port, request_cb, NULL); if (!httpd_serv) { DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 640a5bd9..7dca5f94 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -2306,17 +2306,22 @@ daap_session_is_valid(int id) struct evbuffer * daap_reply_build(const char *uri, const char *user_agent, int is_remote) { - struct httpd_request hreq; - struct evbuffer *out_body; + struct httpd_request *hreq; + struct evbuffer *out_body = NULL; struct daap_session session; int ret; DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri); - out_body = NULL; + hreq = httpd_request_new(NULL, uri, user_agent); + if (!hreq) + { + DPRINTF(E_LOG, L_DAAP, "Error building request: '%s'\n", uri); + goto out; + } - httpd_request_set(&hreq, uri, user_agent); - if (!(&hreq)->handler) + 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; @@ -2325,20 +2330,20 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) memset(&session, 0, sizeof(struct daap_session)); session.is_remote = (bool)is_remote; - hreq.extra_data = &session; + hreq->extra_data = &session; - ret = hreq.handler(&hreq); + ret = hreq->handler(hreq); if (ret < 0) { goto out; } // Take ownership of the reply - out_body = hreq.out_body; - hreq.out_body = NULL; + out_body = hreq->out_body; + hreq->out_body = NULL; out: - httpd_request_unset(&hreq); + httpd_request_free(hreq); return out_body; } diff --git a/src/httpd_internal.h b/src/httpd_internal.h index bab5e07f..76c4bce6 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -41,13 +41,13 @@ 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 evhttp; +struct httpd_server; struct evhttp_connection; struct evhttp_request; struct evkeyvalq; struct httpd_uri_parsed; -typedef struct evhttp httpd_server; +typedef struct httpd_server httpd_server; typedef struct evhttp_connection httpd_connection; typedef struct evhttp_request httpd_backend; typedef struct evkeyvalq httpd_headers; @@ -56,7 +56,7 @@ typedef struct httpd_uri_parsed httpd_uri_parsed; typedef void httpd_backend_data; // Not used for evhttp typedef char *httpd_uri_path_parts[31]; -typedef void (*httpd_general_cb)(httpd_backend *backend, void *arg); +typedef void (*httpd_request_cb)(struct httpd_request *hreq, void *arg); typedef void (*httpd_connection_closecb)(httpd_connection *conn, 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); @@ -186,10 +186,7 @@ void httpd_stream_file(struct httpd_request *hreq, int id); void -httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent); - -void -httpd_request_unset(struct httpd_request *hreq); +httpd_request_handler_set(struct httpd_request *hreq); bool httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime); @@ -290,11 +287,17 @@ httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb c struct event_base * httpd_request_evbase_get(struct httpd_request *hreq); +void +httpd_request_free(struct httpd_request *hreq); + +struct httpd_request * +httpd_request_new(httpd_backend *backend, 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_general_cb cb, void *arg); +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); @@ -344,9 +347,6 @@ httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend int httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend); -void -httpd_backend_preprocess(httpd_backend *backend); - httpd_uri_parsed * httpd_uri_parsed_create(httpd_backend *backend); diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index c948d7f0..49eac9f0 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -1,14 +1,41 @@ +/* + * 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 #include +#include #include +#include #include "misc.h" // For net_evhttp_bind +#include "worker.h" +#include "logger.h" #include "httpd_internal.h" + struct httpd_uri_parsed { struct evhttp_uri *ev_uri; @@ -17,6 +44,19 @@ struct httpd_uri_parsed httpd_uri_path_parts path_parts; }; +struct httpd_server +{ + struct evhttp *evhttp; + httpd_request_cb request_cb; + void *request_cb_arg; +}; + +struct cmdargs +{ + httpd_server *server; + httpd_backend *backend; +}; + const char * httpd_query_value_find(httpd_query *query, const char *key) @@ -107,29 +147,148 @@ httpd_request_evbase_get(struct httpd_request *hreq) return evhttp_connection_get_base(conn); } +void +httpd_request_free(struct httpd_request *hreq) +{ + 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, 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))); + + // Populate hreq by getting values from the backend (or from the caller) + hreq->backend = backend; + if (backend) + { + backend_data = httpd_backend_data_create(backend); + 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; +} + +static void +request_free_cb(httpd_backend *backend, void *arg) +{ + struct httpd_request *hreq = arg; + + httpd_request_free(hreq); +} + +// Executed in a worker thread +static void +gencb_worker_cb(void *arg) +{ + struct cmdargs *cmd = arg; + httpd_server *server = cmd->server; + httpd_backend *backend = cmd->backend; + struct httpd_request *hreq; + + hreq = httpd_request_new(backend, NULL, NULL); + if (!hreq) + { + evhttp_send_error(backend, HTTP_INTERNAL, "Internal error"); + return; + } + + evhttp_request_set_on_complete_cb(backend, request_free_cb, hreq); + + server->request_cb(hreq, server->request_cb_arg); +} + +// Callback from evhttp in httpd thread +static void +gencb_httpd(httpd_backend *backend, void *server) +{ + struct cmdargs cmd; + + cmd.server = server; + cmd.backend = backend; + + // 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; + + // Defer the execution to a worker thread + worker_execute(gencb_worker_cb, &cmd, sizeof(cmd), 0); +} + void httpd_server_free(httpd_server *server) { if (!server) return; - evhttp_free(server); + evhttp_free(server->evhttp); + free(server); } httpd_server * -httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg) +httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_cb cb, void *arg) { + httpd_server *server; int ret; - struct evhttp *server = evhttp_new(evbase); - if (!server) - goto error; + CHECK_NULL(L_HTTPD, server = calloc(1, sizeof(httpd_server))); + CHECK_NULL(L_HTTPD, server->evhttp = evhttp_new(evbase)); - ret = net_evhttp_bind(server, port, "httpd"); + server->request_cb = cb; + server->request_cb_arg = arg; + + ret = net_evhttp_bind(server->evhttp, port, "httpd"); if (ret < 0) goto error; - evhttp_set_gencb(server, cb, arg); + evhttp_set_gencb(server->evhttp, gencb_httpd, server); return server; @@ -141,7 +300,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_c void httpd_server_allow_origin_set(httpd_server *server, bool allow) { - evhttp_set_allowed_methods(server, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS); + evhttp_set_allowed_methods(server->evhttp, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS); } void @@ -223,7 +382,11 @@ httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *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; } @@ -249,14 +412,6 @@ httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend) return 0; } -void -httpd_backend_preprocess(httpd_backend *backend) -{ - // 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; -} - httpd_uri_parsed * httpd_uri_parsed_create(httpd_backend *backend) { diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 85a82f41..65994c49 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -162,35 +162,14 @@ static void streaming_end(void) { struct streaming_session *session; -<<<<<<< HEAD - struct evhttp_connection *evcon; - const char *address; - ev_uint16_t port; -======= ->>>>>>> [httpd] Remove all traces of evhttp from httpd modules pthread_mutex_lock(&streaming_sessions_lck); for (session = streaming_sessions; streaming_sessions; session = streaming_sessions) { -<<<<<<< HEAD - 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); -======= DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); httpd_request_closecb_set(session->hreq, NULL, NULL); -<<<<<<< HEAD - httpd_reply_end_send(session->hreq); ->>>>>>> [httpd] Remove all traces of evhttp from httpd modules -======= httpd_send_reply_end(session->hreq); ->>>>>>> [httpd] Changes to httpd_send_reply_chunk et al streaming_sessions = session->next; free(session); From bd6f38282cc824056ada258e358cfb3d6ac3a5d6 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 17 Jan 2023 17:14:04 +0100 Subject: [PATCH 14/21] [httpd] Main commit that adds multithreading to httpd Also refactor streaming implementation to make it more like an output and to let the design support multithreading. --- src/Makefile.am | 5 +- src/httpd.c | 628 ++++++++++++++++++++++++++--------- src/httpd_daap.c | 14 +- src/httpd_dacp.c | 289 +++++++--------- src/httpd_internal.h | 3 +- src/httpd_jsonapi.c | 2 +- src/httpd_libevhttp.c | 89 ++--- src/httpd_rsp.c | 2 +- src/httpd_streaming.c | 709 +++++++--------------------------------- src/httpd_streaming.h | 16 - src/misc.c | 5 + src/outputs/streaming.c | 525 ++++++++++++++++++++++++++++- src/outputs/streaming.h | 18 + src/worker.c | 2 +- 14 files changed, 1300 insertions(+), 1007 deletions(-) delete mode 100644 src/httpd_streaming.h create mode 100644 src/outputs/streaming.h diff --git a/src/Makefile.am b/src/Makefile.am index a23c032f..454dd7f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,7 +98,7 @@ owntone_SOURCES = main.c \ httpd_daap.c httpd_daap.h \ httpd_dacp.c \ httpd_jsonapi.c \ - httpd_streaming.c httpd_streaming.h \ + httpd_streaming.c \ httpd_oauth.c \ httpd_artworkapi.c \ http.c http.h \ @@ -118,7 +118,8 @@ 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 \ $(SPOTIFY_SRC) \ diff --git a/src/httpd.c b/src/httpd.c index 5694b997..c11645fc 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -36,11 +36,9 @@ #include #include +#include #include // get thread ID -#ifdef HAVE_EVENTFD -# include -#endif #include #include @@ -128,22 +126,376 @@ static const struct content_type_map ext2ctype[] = }; static char webroot_directory[PATH_MAX]; -static 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 httpd_server *httpd_serv; -static pthread_t tid_httpd; static const char *httpd_allow_origin; static int httpd_port; +#define THREADPOOL_NTHREADS 4 + +struct evthr_pool; + +static struct evthr_pool *httpd_threadpool; + + +/* ----------------- Thread handling borrowed from libevhtp ----------------- */ + +#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 + +enum evthr_res { + EVTHR_RES_OK = 0, + EVTHR_RES_BACKLOG, + EVTHR_RES_RETRY, + EVTHR_RES_NOCB, + EVTHR_RES_FATAL +}; + +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 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; + httpd_server *server; + 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); + + CHECK_ERR(L_MAIN, thread->err); + + 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_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; +} + +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; +} + +static 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); +} + +static 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)); +} + +static 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; +} + +static 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; +} + + /* -------------------------------- HELPERS --------------------------------- */ static int @@ -220,7 +572,7 @@ modules_handlers_set(struct httpd_uri_map *uri_map) } static int -modules_init(struct event_base *evbase) +modules_init(void) { struct httpd_module **ptr; struct httpd_module *m; @@ -228,7 +580,7 @@ modules_init(struct event_base *evbase) for (ptr = httpd_modules; *ptr; ptr++) { m = *ptr; - m->initialized = (!m->init || m->init(evbase) == 0); + m->initialized = (!m->init || m->init() == 0); if (!m->initialized) { DPRINTF(E_FATAL, L_HTTPD, "%s init failed\n", m->name); @@ -291,6 +643,35 @@ modules_search(const char *path) /* --------------------------- 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 int +handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) +{ + bool is_cors_preflight; + + is_cors_preflight = ( 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") ); + if (!is_cors_preflight) + return -1; + + cors_headers_add(hreq, allow_origin); + + // In this case there is no reason to go through httpd_send_reply + httpd_backend_reply_send(hreq->backend, HTTP_OK, "OK", NULL); + httpd_request_free(hreq); + return 0; +} + void httpd_request_handler_set(struct httpd_request *hreq) { @@ -750,57 +1131,6 @@ stream_fail_cb(httpd_connection *conn, void *arg) /* ---------------------------- MAIN HTTPD THREAD --------------------------- */ -static void * -httpd(void *arg) -{ - int ret; - - ret = db_perthread_init(); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n"); - - pthread_exit(NULL); - } - - 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 int -handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) -{ - bool is_cors_preflight; - - is_cors_preflight = ( 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") ); - if (!is_cors_preflight) - return -1; - - httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", 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"); - - // In this case there is no reason to go through httpd_send_reply - httpd_backend_reply_send(hreq->backend, HTTP_OK, "OK", NULL); - return 0; -} - static void request_cb(struct httpd_request *hreq, void *arg) { @@ -850,6 +1180,7 @@ httpd_stream_file(struct httpd_request *hreq, int id) void (*stream_cb)(int fd, short event, void *arg); struct stat sb; struct timeval tv; + struct event_base *evbase; const char *param; const char *param_end; const char *client_codecs; @@ -1041,7 +1372,9 @@ httpd_stream_file(struct httpd_request *hreq, int id) goto out_cleanup; } - st->ev = event_new(evbase_httpd, -1, EV_TIMEOUT, stream_cb, st); + evbase = httpd_request_evbase_get(hreq); + + st->ev = event_new(evbase, -1, EV_TIMEOUT, stream_cb, st); evutil_timerclear(&tv); if (!st->ev || (event_add(st->ev, &tv) < 0)) { @@ -1215,8 +1548,7 @@ httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struc (strstr(param, "gzip") || strstr(param, "*")) ); - if (httpd_allow_origin) - httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); + cors_headers_add(hreq, httpd_allow_origin); if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf))) { @@ -1233,11 +1565,15 @@ httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struc { httpd_backend_reply_send(hreq->backend, code, reason, evbuf); } + + httpd_request_free(hreq); } void httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason) { + cors_headers_add(hreq, httpd_allow_origin); + httpd_backend_reply_start_send(hreq->backend, code, reason); } @@ -1251,6 +1587,7 @@ void httpd_send_reply_end(struct httpd_request *hreq) { httpd_backend_reply_end_send(hreq->backend); + httpd_request_free(hreq); } // This is a modified version of evhttp_send_error (credit libevent) @@ -1261,8 +1598,8 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason) httpd_headers_clear(hreq->out_headers); - if (httpd_allow_origin) - httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); + cors_headers_add(hreq, httpd_allow_origin); + httpd_header_add(hreq->out_headers, "Content-Type", "text/html"); httpd_header_add(hreq->out_headers, "Connection", "close"); @@ -1276,6 +1613,8 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason) if (evbuf) evbuffer_free(evbuf); + + httpd_request_free(hreq); } bool @@ -1408,12 +1747,46 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw return -1; } +static void +thread_init_cb(struct evthr *thr, void *shared) +{ + int ret; + + thread_setname(pthread_self(), "httpd"); + + ret = db_perthread_init(); + if (ret < 0) + { + DPRINTF(E_FATAL, L_HTTPD, "Error: DB init failed\n"); + thr->err = EIO; + return; + } + + thr->server = httpd_server_new(thr->evbase, httpd_port, request_cb, NULL); + if (!thr->server) + { + DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); + thr->err = EIO; + return; + } + + // For CORS headers + httpd_server_allow_origin_set(thr->server, httpd_allow_origin); +} + +static void +thread_exit_cb(struct evthr *thr, void *shared) +{ + httpd_server_free(thr->server); + + db_perthread_deinit(); +} + /* Thread: main */ int httpd_init(const char *webroot) { struct stat sb; - int exit_fd; int ret; DPRINTF(E_DBG, L_HTTPD, "Starting web server with root directory '%s'\n", webroot); @@ -1434,42 +1807,18 @@ httpd_init(const char *webroot) return -1; } - CHECK_NULL(L_HTTPD, evbase_httpd = event_base_new()); - -#ifdef HAVE_EVENTFD - CHECK_ERRNO(L_HTTPD, exit_efd = eventfd(0, EFD_CLOEXEC)); - exit_fd = exit_efd; -#else -# ifdef HAVE_PIPE2 - CHECK_ERRNO(L_HTTPD, pipe2(exit_pipe, O_CLOEXEC)); -# else - CHECK_ERRNO(L_HTTPD, pipe(exit_pipe)); -# endif - exit_fd = exit_pipe[0]; -#endif /* HAVE_EVENTFD */ - CHECK_NULL(L_HTTPD, exitev = event_new(evbase_httpd, exit_fd, EV_READ, exit_cb, NULL)); - event_add(exitev, NULL); - + // Read config httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - httpd_serv = httpd_server_new(evbase_httpd, httpd_port, request_cb, NULL); - if (!httpd_serv) - { - DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); - goto server_fail; - } - - // For CORS headers httpd_allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin"); if (strlen(httpd_allow_origin) == 0) httpd_allow_origin = NULL; - httpd_server_allow_origin_set(httpd_serv, httpd_allow_origin); // Prepare modules, e.g. httpd_daap - ret = modules_init(evbase_httpd); + ret = modules_init(); if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n"); - goto modules_fail; + goto error; } #ifdef HAVE_LIBWEBSOCKETS @@ -1477,39 +1826,28 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Websocket init failed\n"); - goto websocket_fail; + goto error; } #endif - ret = pthread_create(&tid_httpd, NULL, httpd, NULL); - if (ret != 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 spawn HTTPd thread: %s\n", strerror(errno)); - goto thread_fail; + DPRINTF(E_LOG, L_HTTPD, "Could not create httpd thread pool\n"); + goto error; } - thread_setname(tid_httpd, "httpd"); + ret = evthr_pool_start(httpd_threadpool); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Could not spawn worker threads\n"); + goto error; + } return 0; - thread_fail: -#ifdef HAVE_LIBWEBSOCKETS - websocket_deinit(); - websocket_fail: -#endif - modules_deinit(); - modules_fail: - httpd_server_free(httpd_serv); - server_fail: - event_free(exitev); -#ifdef HAVE_EVENTFD - close(exit_efd); -#else - close(exit_pipe[0]); - close(exit_pipe[1]); -#endif - event_base_free(evbase_httpd); - + error: + httpd_deinit(); return -1; } @@ -1517,49 +1855,13 @@ httpd_init(const char *webroot) void httpd_deinit(void) { - int ret; - -#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; - } - + // Give modules a chance to hang up connections nicely modules_deinit(); #ifdef HAVE_LIBWEBSOCKETS websocket_deinit(); #endif -#ifdef HAVE_EVENTFD - close(exit_efd); -#else - close(exit_pipe[0]); - close(exit_pipe[1]); -#endif - event_free(exitev); - httpd_server_free(httpd_serv); - event_base_free(evbase_httpd); + evthr_pool_stop(httpd_threadpool); + evthr_pool_free(httpd_threadpool); } diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 7dca5f94..73a2eca1 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -243,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); @@ -315,7 +317,9 @@ update_fail_cb(httpd_connection *conn, void *arg) httpd_request_closecb_set(ur->hreq, NULL, NULL); - httpd_request_backend_free(ur->hreq); // TODO check if still necessary + // Peer won't get this, it is just to make sure hreq and evhttp's request get + // freed + httpd_send_error(ur->hreq, HTTP_BADREQUEST, "Bad Request"); update_remove(ur); } @@ -667,7 +671,7 @@ daap_reply_send(struct httpd_request *hreq, enum daap_reply_result result) switch (result) { case DAAP_REPLY_LOGOUT: - httpd_send_reply(hreq, 204, "Logout Successful", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_NOCONTENT, "Logout Successful", hreq->out_body, 0); break; case DAAP_REPLY_NO_CONTENT: httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); @@ -680,7 +684,7 @@ daap_reply_send(struct httpd_request *hreq, enum daap_reply_result result) httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); break; case DAAP_REPLY_FORBIDDEN: - httpd_send_error(hreq, 403, "Forbidden"); + httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden"); break; case DAAP_REPLY_BAD_REQUEST: httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request"); @@ -2349,7 +2353,7 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) } static int -daap_init(struct event_base *evbase) +daap_init(void) { srand((unsigned)time(NULL)); current_rev = 2; diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index f4c3be4c..2eda58a7 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -30,10 +30,7 @@ #include #include -#ifdef HAVE_EVENTFD -# include -#endif - +#include #include #include "httpd_internal.h" @@ -51,6 +48,7 @@ struct dacp_update_request { struct httpd_request *hreq; + struct event *updateev; struct dacp_update_request *next; }; @@ -125,25 +123,13 @@ 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; @@ -600,6 +586,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); @@ -660,7 +648,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; @@ -726,109 +714,45 @@ 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 event_base *evbase; struct dacp_update_request *ur; - struct evbuffer *evbuf; - struct evbuffer *update; - uint8_t *buf; - size_t len; - int ret; -#ifdef HAVE_EVENTFD - eventfd_t count; + evbase = httpd_request_evbase_get(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)); + CHECK_NULL(L_DACP, ur = calloc(1, sizeof(struct dacp_update_request))); + CHECK_NULL(L_DACP, ur->updateev = event_new(evbase, -1, 0, playstatusupdate_cb, ur)); + ur->hreq = hreq; - 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; - - httpd_request_closecb_set(ur->hreq, 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->hreq, HTTP_OK, "OK", evbuf, 0); - } - else - httpd_send_reply(ur->hreq, 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(httpd_connection *conn, 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; - ur = (struct dacp_update_request *)arg; - - DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n"); - - httpd_request_closecb_set(ur->hreq, 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) @@ -840,8 +764,65 @@ update_fail_cb(httpd_connection *conn, void *arg) p->next = ur->next; } - httpd_request_backend_free(ur->hreq); // TODO check if still necessary - free(ur); + update_request_free(ur); +} + +static void +playstatusupdate_cb(int fd, short what, void *arg) +{ + struct dacp_update_request *ur = arg; + struct evbuffer *update; + int ret; + + CHECK_NULL(L_DACP, update = evbuffer_new()); + + ret = make_playstatusupdate(update, update_current_rev); + if (ret < 0) + goto error; + + httpd_request_closecb_set(ur->hreq, NULL, NULL); + + httpd_send_reply(ur->hreq, HTTP_OK, "OK", update, 0); + + pthread_mutex_lock(&update_request_lck); + update_request_remove(&update_requests, ur); + pthread_mutex_unlock(&update_request_lck); + + error: + evbuffer_free(update); +} + +static void +update_fail_cb(httpd_connection *conn, void *arg) +{ + struct dacp_update_request *ur = arg; + + DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n"); + + httpd_request_closecb_set(ur->hreq, NULL, NULL); + + // Peer won't get this, it is just to make sure hreq and evhttp's request get + // freed + httpd_send_error(ur->hreq, HTTP_BADREQUEST, "Bad Request"); + + 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); } @@ -1047,7 +1028,10 @@ dacp_propset_devicebusy(const char *value, struct httpd_request *hreq) static void dacp_propset_playingtime(const char *value, struct httpd_request *hreq) { + struct event_base *evbase; struct timeval tv; + int seek_target; + intptr_t seek_target_packed; int ret; ret = safe_atoi32(value, &seek_target); @@ -1058,9 +1042,13 @@ 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); + + evbase = httpd_request_evbase_get(hreq); + event_base_once(evbase, -1, EV_TIMEOUT, seek_timer_cb, (void *)seek_target_packed, &tv); } static void @@ -2275,9 +2263,9 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) // 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->out_body); + ret = make_playstatusupdate(hreq->out_body, update_current_rev); if (ret < 0) httpd_send_error(hreq, 500, "Internal Server Error"); else @@ -2287,7 +2275,7 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) } // 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"); @@ -2296,10 +2284,10 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) return -1; } - ur->hreq = hreq; - + 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. @@ -2865,15 +2853,9 @@ dacp_request(struct httpd_request *hreq) hreq->handler(hreq); } -// Forward -static void -dacp_deinit(void); - static int -dacp_init(struct event_base *evbase) +dacp_init(void) { - current_rev = 2; - dummy_mfi.id = DB_MEDIA_FILE_NON_PERSISTENT_ID; dummy_mfi.title = CFG_NAME_UNKNOWN_TITLE; dummy_mfi.artist = CFG_NAME_UNKNOWN_ARTIST; @@ -2886,41 +2868,11 @@ dacp_init(struct event_base *evbase) 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)); - goto error; - } - - CHECK_NULL(L_DACP, updateev = event_new(evbase, update_efd, EV_READ, playstatusupdate_cb, NULL)); -#else -# ifdef HAVE_PIPE2 - int ret = pipe2(update_pipe, O_CLOEXEC); -# else - int ret = pipe(update_pipe); -# endif - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not create update pipe: %s\n", strerror(errno)); - goto error; - } - - CHECK_NULL(L_DACP, updateev = event_new(evbase, update_pipe[0], EV_READ, playstatusupdate_cb, NULL)); -#endif /* HAVE_EVENTFD */ - - event_add(updateev, NULL); - - CHECK_NULL(L_DACP, seek_timer = evtimer_new(evbase, seek_timer_cb, NULL)); - + 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; - - error: - dacp_deinit(); - return -1; } static void @@ -2929,6 +2881,8 @@ dacp_deinit(void) struct dacp_update_request *ur; httpd_connection *conn; + listener_remove(dacp_playstatus_update_handler); + for (ur = update_requests; update_requests; ur = update_requests) { update_requests = ur->next; @@ -2937,23 +2891,8 @@ dacp_deinit(void) conn = httpd_request_connection_get(ur->hreq); httpd_connection_free(conn); // TODO necessary? - free(ur); + update_request_free(ur); } - - listener_remove(dacp_playstatus_update_handler); - - if (seek_timer) - event_free(seek_timer); - - if (updateev) - event_free(updateev); - -#ifdef HAVE_EVENTFD - close(update_efd); -#else - close(update_pipe[0]); - close(update_pipe[1]); -#endif } struct httpd_module httpd_dacp = diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 76c4bce6..267328da 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -108,7 +108,7 @@ struct httpd_module // Pointer to the module's handler definitions struct httpd_uri_map *handlers; - int (*init)(struct event_base *); + int (*init)(void); void (*deinit)(void); void (*request)(struct httpd_request *); }; @@ -138,7 +138,6 @@ struct httpd_request { // Backend private request object httpd_backend *backend; // For storing data that the actual backend doesn't have readily available - // e.g. peer address string for libevhtp httpd_backend_data *backend_data; // User-agent (if available) const char *user_agent; diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 58a5a2f8..7fd3afb4 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4755,7 +4755,7 @@ jsonapi_request(struct httpd_request *hreq) } static int -jsonapi_init(struct event_base *evbase) +jsonapi_init(void) { char *temp_path; diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index 49eac9f0..e8641244 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -22,16 +22,18 @@ #include #include +#include #include +#include // listen() #include #include #include #include #include +#include -#include "misc.h" // For net_evhttp_bind -#include "worker.h" +#include "misc.h" #include "logger.h" #include "httpd_internal.h" @@ -46,17 +48,12 @@ struct httpd_uri_parsed struct httpd_server { + int fd; struct evhttp *evhttp; httpd_request_cb request_cb; void *request_cb_arg; }; -struct cmdargs -{ - httpd_server *server; - httpd_backend *backend; -}; - const char * httpd_query_value_find(httpd_query *query, const char *key) @@ -141,15 +138,20 @@ struct event_base * httpd_request_evbase_get(struct httpd_request *hreq) { httpd_connection *conn = httpd_request_connection_get(hreq); - if (conn) + if (!conn) return NULL; return evhttp_connection_get_base(conn); } +int alloc_count; + void httpd_request_free(struct httpd_request *hreq) { + alloc_count--; + DPRINTF(E_LOG, L_HTTPD, "DEALLOC - COUNT %d\n", alloc_count); + if (!hreq) return; @@ -169,6 +171,9 @@ httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agen CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request))); + alloc_count++; + DPRINTF(E_LOG, L_HTTPD, "ALLOC - COUNT %d\n", alloc_count); + // Populate hreq by getting values from the backend (or from the caller) hreq->backend = backend; if (backend) @@ -217,21 +222,24 @@ httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agen } static void -request_free_cb(httpd_backend *backend, void *arg) +gencb_httpd(httpd_backend *backend, void *arg) { - struct httpd_request *hreq = arg; - - httpd_request_free(hreq); -} - -// Executed in a worker thread -static void -gencb_worker_cb(void *arg) -{ - struct cmdargs *cmd = arg; - httpd_server *server = cmd->server; - httpd_backend *backend = cmd->backend; + 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, NULL, NULL); if (!hreq) @@ -240,35 +248,21 @@ gencb_worker_cb(void *arg) return; } - evhttp_request_set_on_complete_cb(backend, request_free_cb, hreq); - server->request_cb(hreq, server->request_cb_arg); } -// Callback from evhttp in httpd thread -static void -gencb_httpd(httpd_backend *backend, void *server) -{ - struct cmdargs cmd; - - cmd.server = server; - cmd.backend = backend; - - // 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; - - // Defer the execution to a worker thread - worker_execute(gencb_worker_cb, &cmd, sizeof(cmd), 0); -} - void httpd_server_free(httpd_server *server) { if (!server) return; - evhttp_free(server->evhttp); + if (server->fd > 0) + close(server->fd); + + if (server->evhttp) + evhttp_free(server->evhttp); + free(server); } @@ -284,7 +278,16 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c server->request_cb = cb; server->request_cb_arg = arg; - ret = net_evhttp_bind(server->evhttp, port, "httpd"); + server->fd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd"); + if (server->fd <= 0) + goto error; + + // Backlog of 128 is the same 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; diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index edbf3e3f..38702626 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -880,7 +880,7 @@ rsp_request(struct httpd_request *hreq) } static int -rsp_init(struct event_base *evbase) +rsp_init(void) { snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE); diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 65994c49..28fccc13 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,498 +23,160 @@ #include #include #include -#include -#include - #include #include -#include +#include #include +#include #include "httpd_internal.h" -#include "httpd_streaming.h" +#include "outputs/streaming.h" #include "logger.h" #include "conffile.h" -#include "transcode.h" -#include "player.h" -#include "listener.h" -#include "db.h" -// 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 httpd_request *hreq; - struct streaming_session *next; - bool require_icy; // Client requested icy meta - size_t bytes_sent; // Audio bytes sent since last metablock + int fd; + struct event *readev; + bool require_icy; + size_t bytes_sent; }; -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; - -// 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 }; - -// 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]; - -#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) +static struct media_quality streaming_default_quality = { + .sample_rate = 44100, + .bits_per_sample = 16, + .channels = 2, + .bit_rate = 128000, +}; /* 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 + * 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; -static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX]; +static unsigned short streaming_icy_metaint = 16384; +/* ----------------------------- Session helpers ---------------------------- */ + static void -streaming_close_cb(httpd_connection *conn, void *arg) +session_free(struct streaming_session *session) { - struct streaming_session *this; - struct streaming_session *session; - struct streaming_session *prev; - - this = (struct streaming_session *)arg; - - DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", this->hreq->peer_address, (int)this->hreq->peer_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->hreq == this->hreq) - break; - - prev = session; - } - if (!session) + return; + + if (session->readev) { - DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", this->hreq->peer_address, (int)this->hreq->peer_port); - free(this); - pthread_mutex_unlock(&streaming_sessions_lck); - return; + streaming_session_deregister(session->fd); + event_free(session->readev); } - 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 - httpd_send_reply_end(session->hreq); 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) +static struct streaming_session * +session_new(struct httpd_request *hreq, bool require_icy) { struct streaming_session *session; - pthread_mutex_lock(&streaming_sessions_lck); - for (session = streaming_sessions; streaming_sessions; session = streaming_sessions) - { - DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); + CHECK_NULL(L_STREAMING, session = calloc(1, sizeof(struct streaming_session))); - httpd_request_closecb_set(session->hreq, NULL, NULL); - httpd_send_reply_end(session->hreq); + session->hreq = hreq; + session->require_icy = require_icy; - streaming_sessions = session->next; - free(session); - } - pthread_mutex_unlock(&streaming_sessions_lck); - - event_del(streamingev); - event_del(metaev); + return session; } static void -streaming_meta_cb(evutil_socket_t fd, short event, void *arg) +session_end(struct streaming_session *session) { - struct media_quality quality; - struct decode_ctx *decode_ctx; - int ret; + DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); - 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(); + // 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. This also makes + // sure the hreq gets freed. + httpd_send_reply_end(session->hreq); + session_free(session); } -static int -encode_buffer(uint8_t *buffer, size_t size) + +/* ----------------------------- Event callbacks ---------------------------- */ + +static void +conn_close_cb(httpd_connection *conn, void *arg) { - transcode_frame *frame; - int samples; - int ret; + struct streaming_session *session = arg; - 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 - */ -static uint8_t * -streaming_icy_meta_create(uint8_t buf[STREAMING_ICY_METALEN_MAX+1], const char *title, unsigned *buflen) -{ - unsigned titlelen; - unsigned metalen; - uint8_t no16s; - - *buflen = 0; - - if (title == NULL) - { - no16s = 0; - memcpy(buf, &no16s, 1); - - *buflen = 1; - } - else - { - titlelen = strlen(title); - if (titlelen > STREAMING_ICY_METATITLELEN_MAX) - titlelen = STREAMING_ICY_METATITLELEN_MAX; // dont worry about the null byte - - // [0] 1x byte N, indicate the total number of 16 bytes words required - // to represent the meta data - // [1..N] meta data book ended by "StreamTitle='" and "';" - // - // The '15' is strlen of StreamTitle=' + '; - no16s = (15 + titlelen)/16 +1; - metalen = 1 + no16s*16; - memset(buf, 0, metalen); - - memcpy(buf, &no16s, 1); - memcpy(buf+1, (const uint8_t*)"StreamTitle='", 13); - memcpy(buf+14, title, titlelen); - memcpy(buf+14+titlelen, (const uint8_t*)"';", 2); - - *buflen = metalen; - } - - return buf; -} - -static uint8_t * -streaming_icy_meta_splice(const uint8_t *data, size_t datalen, off_t offset, size_t *len) -{ - 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) - - if (data == NULL || datalen == 0) - return NULL; - - 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; + session_end(session); } static void -streaming_player_status_update(void) +read_cb(evutil_socket_t fd, short event, void *arg) { - 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); -} - -static void -streaming_send_cb(evutil_socket_t fd, short event, void *arg) -{ - 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; + 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(hreq->out_body, fd, -1); + if (len < 0 && errno != EAGAIN) { - while (1) - { - ret = read(fd, &rawbuf, sizeof(rawbuf)); - if (ret <= 0) - break; - - if (streaming_player_changed) - { - streaming_player_changed = 0; - streaming_player_status_update(); - } - - ret = encode_buffer(rawbuf, ret); - if (ret < 0) - return; - } - } - // Event timed out, let's see what the player is doing and send silence if it is paused - else - { - if (streaming_player_changed) - { - streaming_player_changed = 0; - streaming_player_status_update(); - } - - if (streaming_player_status.status != PLAY_PAUSED) - return; - - memset(&rawbuf, 0, sizeof(rawbuf)); - ret = encode_buffer(rawbuf, sizeof(rawbuf)); - if (ret < 0) - return; + httpd_request_closecb_set(hreq, NULL, NULL); + session_end(session); + return; } - len = evbuffer_get_length(streaming_encoded_data); - if (len == 0) - return; + httpd_send_reply_chunk(hreq, hreq->out_body, NULL, NULL); - // 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; - - httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL); - - 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); - httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL); - } - else - { - httpd_send_reply_chunk(session->hreq, streaming_encoded_data, NULL, NULL); - } - 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)); - } - } -} - -// Since streaming is a one-trick pony it doesn't need handlers static int -streaming_dummy_handler(struct httpd_request *hreq) +streaming_mp3_handler(struct httpd_request *hreq) { + struct streaming_session *session; + struct event_base *evbase; + const char *name = cfg_getstr(cfg_getsec(cfg, "library"), "name"); + const char *param; + bool require_icy; + char buf[9]; + + param = httpd_header_find(hreq->in_headers, "Icy-MetaData"); + require_icy = (param && strcmp(param, "1") == 0); + if (require_icy) + { + httpd_header_add(hreq->out_headers, "icy-name", name); + snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint); + httpd_header_add(hreq->out_headers, "icy-metaint", buf); + } + + session = session_new(hreq, require_icy); + if (!session) + return -1; + + // Ask streaming output module for a fd to read mp3 from + session->fd = streaming_session_register(STREAMING_FORMAT_MP3, streaming_default_quality); + + CHECK_NULL(L_STREAMING, evbase = httpd_request_evbase_get(hreq)); + CHECK_NULL(L_STREAMING, session->readev = event_new(evbase, session->fd, EV_READ | EV_PERSIST, read_cb, session)); + event_add(session->readev, NULL); + + httpd_request_closecb_set(hreq, conn_close_cb, 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"); + + httpd_send_reply_start(hreq, HTTP_OK, "OK"); + return 0; } @@ -522,7 +184,7 @@ static struct httpd_uri_map streaming_handlers[] = { { .regexp = "^/stream.mp3$", - .handler = streaming_dummy_handler + .handler = streaming_mp3_handler }, { .regexp = NULL, @@ -533,92 +195,34 @@ static struct httpd_uri_map streaming_handlers[] = static void streaming_request(struct httpd_request *hreq) { - struct streaming_session *session; - cfg_t *lib; - const char *name; - const char *param; - bool require_icy = false; - char buf[9]; + int ret; - if (streaming_not_supported) + if (!hreq->handler) { - DPRINTF(E_LOG, L_STREAMING, "Got MP3 streaming request, but cannot encode to MP3\n"); + DPRINTF(E_LOG, L_STREAMING, "Unrecognized path in streaming request: '%s'\n", hreq->uri); - httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); + httpd_send_error(hreq, HTTP_NOTFOUND, NULL); return; } - param = httpd_header_find(hreq->in_headers, "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, hreq->peer_address, (int)hreq->peer_port); - - lib = cfg_getsec(cfg, "library"); - name = cfg_getstr(lib, "name"); - - 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"); - if (require_icy) - { - ++streaming_icy_clients; - httpd_header_add(hreq->out_headers, "icy-name", name); - snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint); - httpd_header_add(hreq->out_headers, "icy-metaint", buf); - } - httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", "*"); - httpd_header_add(hreq->out_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - - httpd_send_reply_start(hreq, HTTP_OK, "OK"); - - session = calloc(1, sizeof(struct streaming_session)); - if (!session) - { - DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n"); - - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - return; - } - - pthread_mutex_lock(&streaming_sessions_lck); - - if (!streaming_sessions) - { - event_add(streamingev, &streaming_silence_tv); - event_add(metaev, NULL); - } - - session->hreq = hreq; - session->next = streaming_sessions; - session->require_icy = require_icy; - session->bytes_sent = 0; - streaming_sessions = session; - - pthread_mutex_unlock(&streaming_sessions_lck); - - httpd_request_closecb_set(hreq, streaming_close_cb, session); + ret = hreq->handler(hreq); + if (ret < 0) + httpd_send_error(hreq, HTTP_INTERNAL, NULL); } static int -streaming_init(struct event_base *evbase) +streaming_init(void) { - int ret; - cfg_t *cfgsec; 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: @@ -626,109 +230,25 @@ streaming_init(struct event_base *evbase) 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, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL)); - CHECK_NULL(L_STREAMING, metaev = event_new(evbase, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL)); - - streaming_icy_clients = 0; - return 0; - - error: - close(streaming_pipe[0]); - close(streaming_pipe[1]); - close(streaming_meta[0]); - close(streaming_meta[1]); - - return -1; -} - -static void -streaming_deinit(void) -{ - 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); } struct httpd_module httpd_streaming = @@ -739,6 +259,5 @@ struct httpd_module httpd_streaming = .fullpaths = { "/stream.mp3", NULL }, .handlers = streaming_handlers, .init = streaming_init, - .deinit = streaming_deinit, .request = streaming_request, }; diff --git a/src/httpd_streaming.h b/src/httpd_streaming.h deleted file mode 100644 index e32d6094..00000000 --- a/src/httpd_streaming.h +++ /dev/null @@ -1,16 +0,0 @@ - -#ifndef __HTTPD_STREAMING_H__ -#define __HTTPD_STREAMING_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); - -#endif /* !__HTTPD_STREAMING_H__ */ diff --git a/src/misc.c b/src/misc.c index 625b09a2..18513e91 100644 --- a/src/misc.c +++ b/src/misc.c @@ -317,6 +317,11 @@ 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 + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); + 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) diff --git a/src/outputs/streaming.c b/src/outputs/streaming.c index a6d02413..9929e7ca 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,530 @@ * 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" + +/* 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. + */ + +// How many times per second we send silence when player is idle (to prevent +// client from hanging up). This value matches the player tick interval. +#define SILENCE_TICKS_PER_SEC 100 + +// 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; +}; + +struct encode_cmdarg +{ + uint8_t *buf; + size_t bufsize; + int samples; + struct media_quality quality; +}; + +static pthread_mutex_t streaming_wanted_lck; +static struct streaming_ctx streaming = +{ + .silencetv = { 0, (1000000 / SILENCE_TICKS_PER_SEC) }, +}; + +extern struct event_base *evbase_player; + + +/* ------------------------------- Helpers ---------------------------------- */ + +static int +pipe_open(struct pipepair *pipe) +{ + 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; + } + + pipe->writefd = fd[1]; + pipe->readfd = fd[0]; + return 0; +} + +static void +pipe_close(struct pipepair *pipe) +{ + if (pipe->readfd >= 0) + close(pipe->readfd); + if (pipe->writefd >= 0) + close(pipe->writefd); + + pipe->writefd = -1; + pipe->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 *pipe, 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(pipe, &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", pipe->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 *pipe) +{ + int ret; + + if (pipe->writefd < 0) + return; + + ret = write(pipe->writefd, buf, buflen); + if (ret < 0) + { + DPRINTF(E_LOG, L_STREAMING, "Error writing to stream pipe %d (format %d): %s\n", pipe->writefd, w->format, strerror(errno)); + wanted_session_remove(w, pipe->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); + 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); + } + pthread_mutex_unlock(&streaming_wanted_lck); + + out: + transcode_frame_free(frame); + free(ctx->buf); +} + + +/* ----------------------------- 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; + } + + ctx.buf = buf; + ctx.bufsize = bufsize; + ctx.samples = samples; + ctx.quality = quality; + + worker_execute(encode_data_cb, &ctx, sizeof(struct encode_cmdarg), 0); +} + +static void +streaming_write(struct output_buffer *obuf) +{ + uint8_t *rawbuf; + + if (!streaming.wanted) + return; + + // Need to make a copy since it will be passed of to the async worker + CHECK_NULL(L_STREAMING, rawbuf = malloc(obuf->data[0].bufsize)); + memcpy(rawbuf, obuf->data[0].buffer, obuf->data[0].bufsize); + + encode_worker_invoke(rawbuf, 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 +silenceev_cb(evutil_socket_t fd, short event, void *arg) +{ + uint8_t *rawbuf; + size_t bufsize; + int samples; + + // TODO what if everyone has disconnected? Check for streaming.wanted? + + samples = streaming.last_quality.sample_rate / SILENCE_TICKS_PER_SEC; + bufsize = STOB(samples, streaming.last_quality.bits_per_sample, streaming.last_quality.channels); + + CHECK_NULL(L_STREAMING, rawbuf = calloc(1, bufsize)); + + encode_worker_invoke(rawbuf, bufsize, samples, streaming.last_quality); + + evtimer_add(streaming.silenceev, &streaming.silencetv); +} + +/* ----------------------------- 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); +} + +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)); + + return 0; +} + +static void +streaming_deinit(void) +{ + event_free(streaming.silenceev); +} struct output_definition output_streaming = { @@ -30,5 +547,7 @@ struct output_definition output_streaming = .type = OUTPUT_TYPE_STREAMING, .priority = 0, .disabled = 0, + .init = streaming_init, + .deinit = streaming_deinit, .write = streaming_write, }; diff --git a/src/outputs/streaming.h b/src/outputs/streaming.h new file mode 100644 index 00000000..901112ae --- /dev/null +++ b/src/outputs/streaming.h @@ -0,0 +1,18 @@ + +#ifndef __STREAMING_H__ +#define __STREAMING_H__ + +#include "misc.h" // struct media_quality + +enum streaming_format +{ + STREAMING_FORMAT_MP3, +}; + +int +streaming_session_register(enum streaming_format format, struct media_quality quality); + +void +streaming_session_deregister(int readfd); + +#endif /* !__STREAMING_H__ */ diff --git a/src/worker.c b/src/worker.c index f665fc40..098fba03 100644 --- a/src/worker.c +++ b/src/worker.c @@ -43,7 +43,7 @@ #include "worker.h" #include "misc.h" -#define THREADPOOL_NTHREADS 4 +#define THREADPOOL_NTHREADS 2 struct evthr_pool; From 4d0c297901019fa28e10cf6abeb6b5c0da8cf446 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 22 Jan 2023 00:33:54 +0100 Subject: [PATCH 15/21] [evhtr] Consolidate pool threading in evthr.c Also reinstate check for server already running --- src/Makefile.am | 1 + src/evthr.c | 467 ++++++++++++++++++++++++++++++++++++++++++ src/evthr.h | 43 ++++ src/httpd.c | 415 ++++--------------------------------- src/httpd_internal.h | 4 +- src/httpd_libevhttp.c | 2 +- src/misc.c | 21 +- src/misc.h | 3 + src/worker.c | 437 +-------------------------------------- 9 files changed, 571 insertions(+), 822 deletions(-) create mode 100644 src/evthr.c create mode 100644 src/evthr.h diff --git a/src/Makefile.am b/src/Makefile.am index 454dd7f2..a64b5299 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -122,6 +122,7 @@ owntone_SOURCES = main.c \ 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/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/httpd.c b/src/httpd.c index c11645fc..fc11489b 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -30,13 +30,11 @@ #include #include #include -#include #include #include #include #include -#include #include // get thread ID #include @@ -49,6 +47,7 @@ #include "conffile.h" #include "misc.h" #include "worker.h" +#include "evthr.h" #include "httpd.h" #include "httpd_internal.h" #include "transcode.h" @@ -133,369 +132,9 @@ static int httpd_port; #define THREADPOOL_NTHREADS 4 -struct evthr_pool; - static struct evthr_pool *httpd_threadpool; -/* ----------------- Thread handling borrowed from libevhtp ----------------- */ - -#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 - -enum evthr_res { - EVTHR_RES_OK = 0, - EVTHR_RES_BACKLOG, - EVTHR_RES_RETRY, - EVTHR_RES_NOCB, - EVTHR_RES_FATAL -}; - -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 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; - httpd_server *server; - 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); - - CHECK_ERR(L_MAIN, thread->err); - - 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_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; -} - -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; -} - -static 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); -} - -static 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)); -} - -static 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; -} - -static 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; -} - - /* -------------------------------- HELPERS --------------------------------- */ static int @@ -1747,37 +1386,44 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw return -1; } +static int +bind_test(short unsigned port) +{ + 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) { - int ret; + struct event_base *evbase; + httpd_server *server; thread_setname(pthread_self(), "httpd"); - ret = db_perthread_init(); - if (ret < 0) - { - DPRINTF(E_FATAL, L_HTTPD, "Error: DB init failed\n"); - thr->err = EIO; - return; - } - - thr->server = httpd_server_new(thr->evbase, httpd_port, request_cb, NULL); - if (!thr->server) - { - DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); - thr->err = EIO; - return; - } + 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(thr->server, httpd_allow_origin); + 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_free(thr->server); + httpd_server *server; + + server = evthr_get_aux(thr); + httpd_server_free(server); db_perthread_deinit(); } @@ -1813,6 +1459,15 @@ httpd_init(const char *webroot) 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; + } + // Prepare modules, e.g. httpd_daap ret = modules_init(); if (ret < 0) diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 267328da..4d28fcef 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -235,9 +235,7 @@ httpd_send_reply_end(struct httpd_request *hreq); void httpd_send_error(struct httpd_request *hreq, int error, const char *reason); -/* - * Redirects to the given path - */ + void httpd_redirect_to(struct httpd_request *hreq, const char *path); diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index e8641244..c4402b1f 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -278,7 +278,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c server->request_cb = cb; server->request_cb_arg = arg; - server->fd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd"); + server->fd = net_bind_with_reuseport(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd"); if (server->fd <= 0) goto error; diff --git a/src/misc.c b/src/misc.c index 18513e91..0f7c7d78 100644 --- a/src/misc.c +++ b/src/misc.c @@ -278,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; @@ -318,7 +318,10 @@ net_bind(short unsigned *port, int type, const char *log_service_name) continue; // Makes us able to attach multiple threads to the same port - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); + 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; @@ -381,6 +384,18 @@ net_bind(short unsigned *port, int type, const char *log_service_name) return -1; } +int +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) { diff --git a/src/misc.h b/src/misc.h index 97f5da9b..d2716e73 100644 --- a/src/misc.h +++ b/src/misc.h @@ -53,6 +53,9 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser int net_bind(short unsigned *port, int type, const char *log_service_name); +int +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; diff --git a/src/worker.c b/src/worker.c index 098fba03..9244a8bf 100644 --- a/src/worker.c +++ b/src/worker.c @@ -31,7 +31,6 @@ #include #include -#include #include #include @@ -41,438 +40,14 @@ #include "db.h" #include "logger.h" #include "worker.h" +#include "evthr.h" #include "misc.h" #define THREADPOOL_NTHREADS 2 -struct evthr_pool; - static struct evthr_pool *worker_threadpool; -/* ----------------- Thread handling borrowed from libevhtp ----------------- */ - -#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 - -enum evthr_res { - EVTHR_RES_OK = 0, - EVTHR_RES_BACKLOG, - EVTHR_RES_RETRY, - EVTHR_RES_NOCB, - EVTHR_RES_FATAL -}; - -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 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); - - CHECK_ERR(L_MAIN, thread->err); - - 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; -} - -static struct event_base * -evthr_get_base(struct evthr * thr) -{ - return thr ? thr->evbase : 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; -} - -static 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); -} - -static 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)); -} - -static 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); -} - -static 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; -} - -static 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; -} - /* ----------------------------- CALLBACK EXECUTION ------------------------- */ /* Worker threads */ @@ -521,15 +96,7 @@ execute(struct evthr *thr, void *arg, void *shared) static void init_cb(struct evthr *thr, void *shared) { - int ret; - - ret = db_perthread_init(); - if (ret < 0) - { - DPRINTF(E_FATAL, L_MAIN, "Error: DB init failed (worker thread)\n"); - thr->err = EIO; - return; - } + CHECK_ERR(L_MAIN, db_perthread_init()); thread_setname(pthread_self(), "worker"); } From e77cb3f94e3f8568ac7c16af859c345ef039ddcd Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 22 Jan 2023 17:23:32 +0100 Subject: [PATCH 16/21] [streaming] ICY handling using output metadata events/callbacks --- src/httpd_libevhttp.c | 30 +++++-- src/httpd_streaming.c | 139 ++++++++++++++++++++++++++---- src/outputs.c | 15 ++++ src/outputs.h | 5 +- src/outputs/streaming.c | 181 +++++++++++++++++++++++++++------------- src/outputs/streaming.h | 5 ++ 6 files changed, 293 insertions(+), 82 deletions(-) diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index c4402b1f..52b577eb 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -23,12 +23,11 @@ #include #include #include -#include +#include // TAILQ_FOREACH #include // listen() #include -#include -#include +#include // flags in struct evhttp #include #include #include @@ -37,6 +36,13 @@ #include "logger.h" #include "httpd_internal.h" +#define DEBUG_ALLOC 1 + +#ifdef DEBUG_ALLOC +#include +static pthread_mutex_t debug_alloc_lck = PTHREAD_MUTEX_INITIALIZER; +static int debug_alloc_count; +#endif struct httpd_uri_parsed { @@ -144,13 +150,15 @@ httpd_request_evbase_get(struct httpd_request *hreq) return evhttp_connection_get_base(conn); } -int alloc_count; - void httpd_request_free(struct httpd_request *hreq) { - alloc_count--; - DPRINTF(E_LOG, L_HTTPD, "DEALLOC - COUNT %d\n", alloc_count); +#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; @@ -171,8 +179,12 @@ httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agen CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request))); - alloc_count++; - DPRINTF(E_LOG, L_HTTPD, "ALLOC - COUNT %d\n", alloc_count); +#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; diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 28fccc13..001899d6 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -40,8 +40,11 @@ struct streaming_session { int fd; struct event *readev; - bool require_icy; + struct evbuffer *readbuf; size_t bytes_sent; + + bool icy_is_requested; + size_t icy_remaining; }; static struct media_quality streaming_default_quality = { @@ -51,13 +54,110 @@ static struct media_quality streaming_default_quality = { .bit_rate = 128000, }; -/* 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 - */ + +/* ------------------------------ ICY metadata -------------------------------*/ + +// 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 static unsigned short streaming_icy_metaint = 16384; +static pthread_mutex_t streaming_metadata_lck; +static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX]; + + +// 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 * +icy_meta_create(uint8_t buf[STREAMING_ICY_METALEN_MAX+1], unsigned *buflen, const char *title) +{ + unsigned titlelen; + unsigned metalen; + uint8_t no16s; + + *buflen = 0; + + if (title == NULL) + { + no16s = 0; + memcpy(buf, &no16s, 1); + + *buflen = 1; + } + else + { + titlelen = strlen(title); + if (titlelen > STREAMING_ICY_METATITLELEN_MAX) + titlelen = STREAMING_ICY_METATITLELEN_MAX; // dont worry about the null byte + + // [0] 1x byte N, indicate the total number of 16 bytes words required + // to represent the meta data + // [1..N] meta data book ended by "StreamTitle='" and "';" + // + // The '15' is strlen of StreamTitle=' + '; + no16s = (15 + titlelen)/16 +1; + metalen = 1 + no16s*16; + memset(buf, 0, metalen); + + memcpy(buf, &no16s, 1); + memcpy(buf+1, (const uint8_t*)"StreamTitle='", 13); + memcpy(buf+14, title, titlelen); + memcpy(buf+14+titlelen, (const uint8_t*)"';", 2); + + *buflen = metalen; + } + + return buf; +} + +static void +icy_meta_splice(struct evbuffer *out, struct evbuffer *in, size_t *icy_remaining) +{ + uint8_t meta[STREAMING_ICY_METALEN_MAX + 1]; + unsigned metalen; + size_t buf_remaining; + size_t consume; + + 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); + + 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 +icy_metadata_cb(char *metadata) +{ + pthread_mutex_lock(&streaming_metadata_lck); + snprintf(streaming_icy_title, sizeof(streaming_icy_title), "%s", metadata); + pthread_mutex_unlock(&streaming_metadata_lck); +} + /* ----------------------------- Session helpers ---------------------------- */ @@ -73,18 +173,21 @@ session_free(struct streaming_session *session) event_free(session->readev); } + evbuffer_free(session->readbuf); free(session); } static struct streaming_session * -session_new(struct httpd_request *hreq, bool require_icy) +session_new(struct httpd_request *hreq, bool icy_is_requested) { struct streaming_session *session; CHECK_NULL(L_STREAMING, session = calloc(1, sizeof(struct streaming_session))); + CHECK_NULL(L_STREAMING, session->readbuf = evbuffer_new()); session->hreq = hreq; - session->require_icy = require_icy; + session->icy_is_requested = icy_is_requested; + session->icy_remaining = streaming_icy_metaint; return session; } @@ -121,7 +224,7 @@ read_cb(evutil_socket_t fd, short event, void *arg) CHECK_NULL(L_STREAMING, hreq = session->hreq); - len = evbuffer_read(hreq->out_body, fd, -1); + len = evbuffer_read(session->readbuf, fd, -1); if (len < 0 && errno != EAGAIN) { httpd_request_closecb_set(hreq, NULL, NULL); @@ -129,6 +232,11 @@ read_cb(evutil_socket_t fd, short event, void *arg) return; } + if (session->icy_is_requested) + icy_meta_splice(hreq->out_body, session->readbuf, &session->icy_remaining); + else + evbuffer_add_buffer(hreq->out_body, session->readbuf); + httpd_send_reply_chunk(hreq, hreq->out_body, NULL, NULL); session->bytes_sent += len; @@ -144,19 +252,19 @@ streaming_mp3_handler(struct httpd_request *hreq) struct event_base *evbase; const char *name = cfg_getstr(cfg_getsec(cfg, "library"), "name"); const char *param; - bool require_icy; + bool icy_is_requested; char buf[9]; param = httpd_header_find(hreq->in_headers, "Icy-MetaData"); - require_icy = (param && strcmp(param, "1") == 0); - if (require_icy) + icy_is_requested = (param && strcmp(param, "1") == 0); + if (icy_is_requested) { httpd_header_add(hreq->out_headers, "icy-name", name); - snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint); + snprintf(buf, sizeof(buf), "%d", streaming_icy_metaint); httpd_header_add(hreq->out_headers, "icy-metaint", buf); } - session = session_new(hreq, require_icy); + session = session_new(hreq, icy_is_requested); if (!session) return -1; @@ -248,6 +356,9 @@ streaming_init(void) else DPRINTF(E_INFO, L_STREAMING, "Unsupported icy_metaint=%d, supported range: 4096..131072, defaulting to %d\n", val, streaming_icy_metaint); + CHECK_ERR(L_STREAMING, mutex_init(&streaming_metadata_lck)); + streaming_metadatacb_register(icy_metadata_cb); + return 0; } 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 9929e7ca..0449734f 100644 --- a/src/outputs/streaming.c +++ b/src/outputs/streaming.c @@ -34,6 +34,7 @@ #include "worker.h" #include "transcode.h" #include "logger.h" +#include "db.h" /* About * @@ -43,9 +44,14 @@ * player, but there are clients, it instead writes silence to the fd. */ -// How many times per second we send silence when player is idle (to prevent -// client from hanging up). This value matches the player tick interval. -#define SILENCE_TICKS_PER_SEC 100 +// 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 @@ -79,6 +85,13 @@ struct streaming_ctx 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 @@ -86,13 +99,16 @@ 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 = { 0, (1000000 / SILENCE_TICKS_PER_SEC) }, + .silencetv = { STREAMING_SILENCE_INTERVAL, 0 }, }; extern struct event_base *evbase_player; @@ -394,9 +410,15 @@ encode_data_cb(void *arg) } 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 @@ -415,6 +437,9 @@ encode_data_cb(void *arg) 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: @@ -422,67 +447,25 @@ encode_data_cb(void *arg) free(ctx->buf); } - -/* ----------------------------- Thread: Player ----------------------------- */ - -static void -encode_worker_invoke(uint8_t *buf, size_t bufsize, int samples, struct media_quality quality) +static void * +streaming_metadata_prepare(struct output_metadata *metadata) { - struct encode_cmdarg ctx; + struct db_queue_item *queue_item; + char *title; - if (quality.channels == 0) + queue_item = db_queue_fetch_byitemid(metadata->item_id); + if (!queue_item) { - DPRINTF(E_LOG, L_STREAMING, "Streaming quality is zero (%d/%d/%d)\n", - quality.sample_rate, quality.bits_per_sample, quality.channels); - return; + DPRINTF(E_LOG, L_STREAMING, "Could not fetch queue item id %d for new metadata\n", metadata->item_id); + return NULL; } - ctx.buf = buf; - ctx.bufsize = bufsize; - ctx.samples = samples; - ctx.quality = quality; + title = safe_asprintf("%s - %s", queue_item->title, queue_item->artist); + free_queue_item(queue_item, 0); - worker_execute(encode_data_cb, &ctx, sizeof(struct encode_cmdarg), 0); + return title; } -static void -streaming_write(struct output_buffer *obuf) -{ - uint8_t *rawbuf; - - if (!streaming.wanted) - return; - - // Need to make a copy since it will be passed of to the async worker - CHECK_NULL(L_STREAMING, rawbuf = malloc(obuf->data[0].bufsize)); - memcpy(rawbuf, obuf->data[0].buffer, obuf->data[0].bufsize); - - encode_worker_invoke(rawbuf, 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 -silenceev_cb(evutil_socket_t fd, short event, void *arg) -{ - uint8_t *rawbuf; - size_t bufsize; - int samples; - - // TODO what if everyone has disconnected? Check for streaming.wanted? - - samples = streaming.last_quality.sample_rate / SILENCE_TICKS_PER_SEC; - bufsize = STOB(samples, streaming.last_quality.bits_per_sample, streaming.last_quality.channels); - - CHECK_NULL(L_STREAMING, rawbuf = calloc(1, bufsize)); - - encode_worker_invoke(rawbuf, bufsize, samples, streaming.last_quality); - - evtimer_add(streaming.silenceev, &streaming.silencetv); -} /* ----------------------------- Thread: httpd ------------------------------ */ @@ -526,11 +509,90 @@ streaming_session_deregister(int readfd) 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, mutex_init(&streaming_wanted_lck)); + CHECK_ERR(L_STREAMING, pthread_cond_init(&streaming_sequence_cond, NULL)); return 0; } @@ -541,6 +603,7 @@ streaming_deinit(void) event_free(streaming.silenceev); } + struct output_definition output_streaming = { .name = "mp3 streaming", @@ -550,4 +613,6 @@ struct output_definition output_streaming = .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 index 901112ae..34673345 100644 --- a/src/outputs/streaming.h +++ b/src/outputs/streaming.h @@ -4,6 +4,8 @@ #include "misc.h" // struct media_quality +typedef void (*streaming_metadatacb)(char *metadata); + enum streaming_format { STREAMING_FORMAT_MP3, @@ -15,4 +17,7 @@ streaming_session_register(enum streaming_format format, struct media_quality qu void streaming_session_deregister(int readfd); +void +streaming_metadatacb_register(streaming_metadatacb cb); + #endif /* !__STREAMING_H__ */ From 7841e336b39516ebff5fa2ef6742853a6dcda85b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 19 Jan 2023 23:58:21 +0100 Subject: [PATCH 17/21] [main] Make libevent_pthreads non-optional --- configure.ac | 10 ++++------ src/main.c | 6 +----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 16822734..af94cb17 100644 --- a/configure.ac +++ b/configure.ac @@ -148,7 +148,10 @@ OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0], [AC_MSG_RESULT([[runtime will tell]])]) ]) -OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT], [libevent >= 2.1.4], [event_base_new], [event2/event.h]) +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([[ @@ -263,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/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(); From a2c63a5bac3e08908999421fc02cfd6eb4f6355a Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 26 Jan 2023 21:00:40 +0100 Subject: [PATCH 18/21] [httpd] Check for syscall() presence and fix small memleak --- configure.ac | 2 +- src/httpd.c | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index af94cb17..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]])]) diff --git a/src/httpd.c b/src/httpd.c index fc11489b..9ece0c90 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -35,7 +35,9 @@ #include #include -#include // get thread ID +#ifdef HAVE_SYSCALL +#include // get thread ID +#endif #include @@ -587,12 +589,13 @@ serve_file(struct httpd_request *hreq) /* ---------------------------- STREAM HANDLING ----------------------------- */ static void -stream_end(struct stream_ctx *st, int failed) +stream_end(struct stream_ctx *st) { httpd_request_closecb_set(st->hreq, NULL, NULL); - if (!failed) - httpd_send_reply_end(st->hreq); + // Alwayss send reply, even if connection failed, otherwise we memleak hreq + // and possibly the evhttp req as well. + httpd_send_reply_end(st->hreq); evbuffer_free(st->evbuf); event_free(st->ev); @@ -638,7 +641,7 @@ stream_chunk_resched_cb(httpd_connection *conn, void *arg) { DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n"); - stream_end(st, 0); + stream_end(st); } } @@ -660,7 +663,7 @@ 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; } @@ -704,7 +707,7 @@ stream_chunk_xcode_cb(int fd, short event, void *arg) { DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming (xcode)\n"); - stream_end(st, 0); + stream_end(st); return; } } @@ -720,7 +723,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) if (st->end_offset && (st->offset > st->end_offset)) { - stream_end(st, 0); + stream_end(st); return; } @@ -737,7 +740,7 @@ 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; } @@ -764,7 +767,7 @@ stream_fail_cb(httpd_connection *conn, void *arg) /* Stop streaming */ event_del(st->ev); - stream_end(st, 1); + stream_end(st); } @@ -795,7 +798,11 @@ request_cb(struct httpd_request *hreq, void *arg) httpd_request_handler_set(hreq); if (hreq->module) { +#ifdef HAVE_SYSCALL DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s' (thread %ld)\n", hreq->module->name, hreq->uri, syscall(SYS_gettid)); +#else + DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s'\n", hreq->module->name, hreq->uri); +#endif hreq->module->request(hreq); } else From 81922e147e7712c8ea491723e163b79153e49038 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 26 Jan 2023 22:33:23 +0100 Subject: [PATCH 19/21] [streaming] Fix BSD name collision (pipe) --- src/outputs/streaming.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/outputs/streaming.c b/src/outputs/streaming.c index 0449734f..d1b34d00 100644 --- a/src/outputs/streaming.c +++ b/src/outputs/streaming.c @@ -117,7 +117,7 @@ extern struct event_base *evbase_player; /* ------------------------------- Helpers ---------------------------------- */ static int -pipe_open(struct pipepair *pipe) +pipe_open(struct pipepair *p) { int fd[2]; int ret; @@ -138,21 +138,21 @@ pipe_open(struct pipepair *pipe) return -1; } - pipe->writefd = fd[1]; - pipe->readfd = fd[0]; + p->writefd = fd[1]; + p->readfd = fd[0]; return 0; } static void -pipe_close(struct pipepair *pipe) +pipe_close(struct pipepair *p) { - if (pipe->readfd >= 0) - close(pipe->readfd); - if (pipe->writefd >= 0) - close(pipe->writefd); + if (p->readfd >= 0) + close(p->readfd); + if (p->writefd >= 0) + close(p->writefd); - pipe->writefd = -1; - pipe->readfd = -1; + p->writefd = -1; + p->readfd = -1; } static void @@ -257,7 +257,7 @@ wanted_find_byreadfd(struct streaming_wanted *wanted, int readfd) } static int -wanted_session_add(struct pipepair *pipe, struct streaming_wanted *w) +wanted_session_add(struct pipepair *p, struct streaming_wanted *w) { int ret; int i; @@ -271,7 +271,7 @@ wanted_session_add(struct pipepair *pipe, struct streaming_wanted *w) if (ret < 0) return -1; - memcpy(pipe, &w->pipes[i], sizeof(struct pipepair)); + memcpy(p, &w->pipes[i], sizeof(struct pipepair)); break; } @@ -282,7 +282,7 @@ wanted_session_add(struct pipepair *pipe, struct streaming_wanted *w) } w->refcount++; - DPRINTF(E_DBG, L_STREAMING, "Session register readfd %d, wanted->refcount=%d\n", pipe->readfd, w->refcount); + DPRINTF(E_DBG, L_STREAMING, "Session register readfd %d, wanted->refcount=%d\n", p->readfd, w->refcount); return 0; } @@ -375,18 +375,18 @@ encode_frame(struct streaming_wanted *w, struct media_quality quality_in, transc } static void -encode_write(uint8_t *buf, size_t buflen, struct streaming_wanted *w, struct pipepair *pipe) +encode_write(uint8_t *buf, size_t buflen, struct streaming_wanted *w, struct pipepair *p) { int ret; - if (pipe->writefd < 0) + if (p->writefd < 0) return; - ret = write(pipe->writefd, buf, buflen); + ret = write(p->writefd, buf, buflen); if (ret < 0) { - DPRINTF(E_LOG, L_STREAMING, "Error writing to stream pipe %d (format %d): %s\n", pipe->writefd, w->format, strerror(errno)); - wanted_session_remove(w, pipe->readfd); + 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); } } From 18a80f15ddee7b696ee47294791b3f2d2617bc9b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 27 Jan 2023 23:13:46 +0100 Subject: [PATCH 20/21] [httpd] Multithread solution using worker threads instead of httpd threads Using worker threads instead of httpd threads means that we, not libevent, decide which requests get handled by which threads. This means that we can make sure blocking requests (e.g. volume changes) don't get in the way of realtime(ish) stuff like mp3 streaming. Includes refactor of httpd_stream_file() since it was a bit of a monster. --- src/commands.c | 4 +- src/httpd.c | 565 +++++++++++++++++++---------------------- src/httpd_artworkapi.c | 6 +- src/httpd_daap.c | 72 ++---- src/httpd_dacp.c | 137 ++++------ src/httpd_internal.h | 65 ++--- src/httpd_jsonapi.c | 6 +- src/httpd_libevhttp.c | 251 +++++++++++++----- src/httpd_rsp.c | 47 +--- src/httpd_streaming.c | 33 +-- src/worker.c | 14 +- src/worker.h | 9 +- 12 files changed, 611 insertions(+), 598 deletions(-) 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/httpd.c b/src/httpd.c index 9ece0c90..4d1b241e 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -99,8 +98,6 @@ struct content_type_map { struct stream_ctx { struct httpd_request *hreq; - uint8_t *buf; - struct evbuffer *evbuf; struct event *ev; int id; int fd; @@ -132,7 +129,18 @@ static const char *httpd_allow_origin; static int httpd_port; -#define THREADPOOL_NTHREADS 4 +// 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; @@ -294,23 +302,12 @@ cors_headers_add(struct httpd_request *hreq, const char *allow_origin) httpd_header_add(hreq->out_headers, "Access-Control-Allow-Headers", "authorization"); } -static int -handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) +static bool +is_cors_preflight(struct httpd_request *hreq, const char *allow_origin) { - bool is_cors_preflight; - - is_cors_preflight = ( 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") ); - if (!is_cors_preflight) - return -1; - - cors_headers_add(hreq, allow_origin); - - // In this case there is no reason to go through httpd_send_reply - httpd_backend_reply_send(hreq->backend, HTTP_OK, "OK", NULL); - httpd_request_free(hreq); - return 0; + 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 @@ -337,6 +334,7 @@ httpd_request_handler_set(struct httpd_request *hreq) continue; hreq->handler = map->handler; + hreq->is_async = !(map->flags & HTTPD_HANDLER_REALTIME); break; } } @@ -346,7 +344,7 @@ 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", NULL, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_MOVETEMP, "Moved", HTTPD_SEND_NO_GZIP); } /* @@ -430,7 +428,6 @@ serve_file(struct httpd_request *hreq) char path[PATH_MAX]; char deref[PATH_MAX]; char *ctype; - struct evbuffer *evbuf; struct stat sb; int fd; int i; @@ -511,23 +508,14 @@ serve_file(struct httpd_request *hreq) { DPRINTF(E_WARN, L_HTTPD, "Access to file outside the web root dir forbidden: %s\n", deref); - httpd_send_error(hreq, 403, "Forbidden"); + httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden"); return; } if (httpd_request_not_modified_since(hreq, sb.st_mtime)) { - httpd_send_reply(hreq, 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(hreq, HTTP_SERVUNAVAIL, "Internal error"); + httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, HTTPD_SEND_NO_GZIP); return; } @@ -537,11 +525,10 @@ serve_file(struct httpd_request *hreq) DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", deref, strerror(errno)); httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); - evbuffer_free(evbuf); 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"); @@ -549,7 +536,7 @@ serve_file(struct httpd_request *hreq) } while ((ret = read(fd, buf, sizeof(buf))) > 0) - evbuffer_add(evbuf, buf, ret); + evbuffer_add(hreq->out_body, buf, ret); if (ret < 0) { @@ -573,42 +560,53 @@ serve_file(struct httpd_request *hreq) httpd_header_add(hreq->out_headers, "Content-Type", ctype); - httpd_send_reply(hreq, 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(hreq, HTTP_SERVUNAVAIL, "Internal error"); - evbuffer_free(evbuf); 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_chunk_resched_cb(httpd_connection *conn, void *arg) +{ + struct stream_ctx *st = arg; + + // TODO not thread safe if st was freed in worker thread, but maybe not possible? + event_active(st->ev, 0, 0); +} + +static void +stream_free(struct stream_ctx *st) +{ + if (!st) + return; + + 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) { - httpd_request_closecb_set(st->hreq, NULL, NULL); + DPRINTF(E_DBG, L_HTTPD, "Ending stream %d\n", st->id); - // Alwayss send reply, even if connection failed, otherwise we memleak hreq - // and possibly the evhttp req as well. httpd_send_reply_end(st->hreq); - evbuffer_free(st->evbuf); - event_free(st->ev); - - if (st->xcode) - transcode_cleanup(&st->xcode); - else - { - free(st->buf); - close(st->fd); - } - - free(st); + stream_free(st); } static void @@ -626,36 +624,134 @@ stream_end_register(struct stream_ctx *st) } } -static void -stream_chunk_resched_cb(httpd_connection *conn, 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); + httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); + goto error; } + + 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; } 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) @@ -669,58 +765,45 @@ stream_chunk_xcode_cb(int fd, short event, void *arg) 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; - httpd_send_reply_chunk(st->hreq, st->evbuf, stream_chunk_resched_cb, st); + 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); - 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); @@ -732,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) @@ -746,9 +829,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) DPRINTF(E_DBG, L_HTTPD, "Read %d bytes; streaming file id %d\n", ret, st->id); - evbuffer_add(st->evbuf, st->buf, ret); - - httpd_send_reply_chunk(st->hreq, st->evbuf, stream_chunk_resched_cb, st); + httpd_send_reply_chunk(st->hreq, stream_chunk_resched_cb, st); st->offset += ret; @@ -756,33 +837,41 @@ stream_chunk_raw_cb(int fd, short event, void *arg) } static void -stream_fail_cb(httpd_connection *conn, void *arg) +stream_fail_cb(struct httpd_request *hreq, 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); + stream_free(st); } -/* ---------------------------- MAIN HTTPD THREAD --------------------------- */ +/* ---------------------------- REQUEST CALLBACKS --------------------------- */ +// Worker thread, invoked by request_cb() below +static void +request_async_cb(void *arg) +{ + struct httpd_request *hreq = *(struct httpd_request **)arg; + +#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) { - // Did we get a CORS preflight request? - if (handle_cors_preflight(hreq, httpd_allow_origin) == 0) + if (is_cors_preflight(hreq, httpd_allow_origin)) { + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); return; } - - if (!hreq->uri || !hreq->uri_parsed) + else if (!hreq->uri || !hreq->uri_parsed) { DPRINTF(E_WARN, L_HTTPD, "Invalid URI in request: '%s'\n", hreq->uri); httpd_redirect_to(hreq, "/"); @@ -796,13 +885,14 @@ request_cb(struct httpd_request *hreq, void *arg) } httpd_request_handler_set(hreq); - if (hreq->module) + if (hreq->module && hreq->is_async) + { + worker_execute(request_async_cb, &hreq, sizeof(struct httpd_request *), 0); + } + else if (hreq->module) { -#ifdef HAVE_SYSCALL - DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s' (thread %ld)\n", hreq->module->name, hreq->uri, syscall(SYS_gettid)); -#else DPRINTF(E_DBG, hreq->module->logdomain, "%s request: '%s'\n", hreq->module->name, hreq->uri); -#endif + hreq->evbase = httpd_backend_evbase_get(hreq->backend); hreq->module->request(hreq); } else @@ -811,35 +901,26 @@ request_cb(struct httpd_request *hreq, void *arg) DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", hreq->uri); serve_file(hreq); } + + // Don't touch hreq here, if async it has been passed to a worker thread } /* ------------------------------- HTTPD API -------------------------------- */ -/* Thread: httpd */ void httpd_stream_file(struct httpd_request *hreq, 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 event_base *evbase; + struct media_file_info *mfi = NULL; + struct stream_ctx *st = NULL; const char *param; const char *param_end; - 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; - param = httpd_header_find(hreq->in_headers, "Range"); if (param) { @@ -880,103 +961,45 @@ httpd_stream_file(struct httpd_request *hreq, int id) DPRINTF(E_LOG, L_HTTPD, "Item %d not found\n", id); httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); - return; + 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); - httpd_send_error(hreq, 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"); - - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - goto out_free_mfi; - } - memset(st, 0, sizeof(struct stream_ctx)); - st->fd = -1; - - client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs"); - - transcode = transcode_needed(hreq->user_agent, client_codecs, mfi->codectype); + 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->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 out_free_st; - } + st = stream_new_transcode(mfi, hreq, offset, end_offset, stream_chunk_xcode_cb); + if (!st) + goto error; 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; - httpd_send_error(hreq, 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)); - - httpd_send_error(hreq, 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)); - - httpd_send_error(hreq, 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)); - - httpd_send_error(hreq, 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"); @@ -986,10 +1009,9 @@ httpd_stream_file(struct httpd_request *hreq, int id) 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!). - */ + // 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); @@ -1000,47 +1022,11 @@ httpd_stream_file(struct httpd_request *hreq, int id) } } - st->evbuf = evbuffer_new(); - if (!st->evbuf) - { - DPRINTF(E_LOG, L_HTTPD, "Could not allocate an evbuffer for streaming\n"); - - httpd_send_error(hreq, 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"); - - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - goto out_cleanup; - } - - evbase = httpd_request_evbase_get(hreq); - - st->ev = event_new(evbase, -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"); - - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - goto out_cleanup; - } - - st->id = mfi->id; - st->start_offset = offset; - st->stream_size = st->size; - st->hreq = hreq; - 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); @@ -1054,11 +1040,6 @@ httpd_stream_file(struct httpd_request *hreq, int id) } 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, @@ -1080,7 +1061,7 @@ httpd_stream_file(struct httpd_request *hreq, int id) #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 ) @@ -1088,26 +1069,15 @@ httpd_stream_file(struct httpd_request *hreq, int id) } #endif - httpd_request_closecb_set(hreq, 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); } @@ -1178,10 +1148,18 @@ 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 httpd_request *hreq, 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 evbuffer *save; const char *param; int do_gzip; @@ -1189,30 +1167,24 @@ httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struc return; do_gzip = ( (!(flags & HTTPD_SEND_NO_GZIP)) && - evbuf && (evbuffer_get_length(evbuf) > 512) && + (evbuffer_get_length(hreq->out_body) > 512) && (param = httpd_header_find(hreq->in_headers, "Accept-Encoding")) && (strstr(param, "gzip") || strstr(param, "*")) ); 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"); httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); - httpd_backend_reply_send(hreq->backend, code, reason, gzbuf); - evbuffer_free(gzbuf); - - // Drain original buffer, as would be after evhttp_send_reply() - evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); - } - else - { - httpd_backend_reply_send(hreq->backend, code, reason, evbuf); + save = hreq->out_body; + hreq->out_body = gzbuf; + evbuffer_free(save); } - httpd_request_free(hreq); + httpd_send(hreq, HTTPD_REPLY_COMPLETE, code, reason, NULL, NULL); } void @@ -1220,28 +1192,26 @@ httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason) { cors_headers_add(hreq, httpd_allow_origin); - httpd_backend_reply_start_send(hreq->backend, code, reason); + httpd_send(hreq, HTTPD_REPLY_START, code, reason, NULL, NULL); } void -httpd_send_reply_chunk(struct httpd_request *hreq, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) +httpd_send_reply_chunk(struct httpd_request *hreq, httpd_connection_chunkcb cb, void *arg) { - httpd_backend_reply_chunk_send(hreq->backend, evbuf, cb, arg); + httpd_send(hreq, HTTPD_REPLY_CHUNK, 0, NULL, cb, arg); } void httpd_send_reply_end(struct httpd_request *hreq) { - httpd_backend_reply_end_send(hreq->backend); - httpd_request_free(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 httpd_request *hreq, int error, const char *reason) { - struct evbuffer *evbuf; - + evbuffer_drain(hreq->out_body, -1); httpd_headers_clear(hreq->out_headers); cors_headers_add(hreq, httpd_allow_origin); @@ -1249,18 +1219,9 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason) httpd_header_add(hreq->out_headers, "Content-Type", "text/html"); httpd_header_add(hreq->out_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); + evbuffer_add_printf(hreq->out_body, ERR_PAGE, error, reason, reason); - httpd_backend_reply_send(hreq->backend, error, reason, evbuf); - - if (evbuf) - evbuffer_free(evbuf); - - httpd_request_free(hreq); + httpd_send(hreq, HTTPD_REPLY_COMPLETE, error, reason, NULL, NULL); } bool @@ -1277,7 +1238,7 @@ httpd_admin_check_auth(struct httpd_request *hreq) { DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: No password set in the config\n", hreq->uri); - httpd_send_error(hreq, 403, "Forbidden"); + httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden"); return false; } @@ -1300,7 +1261,6 @@ 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) { - struct evbuffer *evbuf; char header[256]; const char *auth; char *authuser; @@ -1375,20 +1335,11 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw return -1; } - evbuf = evbuffer_new(); - if (!evbuf) - { - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - return -1; - } - httpd_header_add(hreq->out_headers, "WWW-Authenticate", header); - evbuffer_add_printf(evbuf, ERR_PAGE, 401, "Unauthorized", "Authorization required"); + evbuffer_add_printf(hreq->out_body, ERR_PAGE, HTTP_UNAUTHORIZED, "Unauthorized", "Authorization required"); - httpd_send_reply(hreq, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP); - - evbuffer_free(evbuf); + httpd_send_reply(hreq, HTTP_UNAUTHORIZED, "Unauthorized", HTTPD_SEND_NO_GZIP); return -1; } diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index d1292f87..48ed9161 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -166,13 +166,13 @@ artworkapi_request(struct httpd_request *hreq) switch (status_code) { case HTTP_OK: /* 200 OK */ - httpd_send_reply(hreq, status_code, "OK", hreq->out_body, 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(hreq, status_code, "No Content", hreq->out_body, 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(hreq, 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(hreq, status_code, "Bad Request"); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 73a2eca1..800283ae 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -284,42 +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 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 */ - httpd_request_closecb_set(ur->hreq, NULL, NULL); - - httpd_send_reply(ur->hreq, HTTP_OK, "OK", reply, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); update_remove(ur); } static void -update_fail_cb(httpd_connection *conn, void *arg) +update_fail_cb(struct httpd_request *hreq, void *arg) { - 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"); - httpd_request_closecb_set(ur->hreq, NULL, NULL); - - // Peer won't get this, it is just to make sure hreq and evhttp's request get - // freed - httpd_send_error(ur->hreq, HTTP_BADREQUEST, "Bad Request"); update_remove(ur); } @@ -671,17 +657,17 @@ daap_reply_send(struct httpd_request *hreq, enum daap_reply_result result) switch (result) { case DAAP_REPLY_LOGOUT: - httpd_send_reply(hreq, HTTP_NOCONTENT, "Logout Successful", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_NOCONTENT, "Logout Successful", 0); break; case DAAP_REPLY_NO_CONTENT: - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, 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, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); break; case DAAP_REPLY_OK_NO_GZIP: case DAAP_REPLY_ERROR: - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); break; case DAAP_REPLY_FORBIDDEN: httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden"); @@ -724,7 +710,7 @@ daap_request_authorize(struct httpd_request *hreq) { DPRINTF(E_LOG, L_DAAP, "Unauthorized request from '%s', DAAP session not found: '%s'\n", hreq->peer_address, hreq->uri); - httpd_send_error(hreq, 401, "Unauthorized"); + httpd_send_error(hreq, HTTP_UNAUTHORIZED, "Unauthorized");; return -1; } @@ -760,7 +746,7 @@ 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 httpd_request_connection_get(hreq) is not null. */ +/* that hreq->backend is not null. */ static enum daap_reply_result daap_reply_server_info(struct httpd_request *hreq) @@ -773,7 +759,7 @@ daap_reply_server_info(struct httpd_request *hreq) int mpro; int apro; - if (!httpd_request_connection_get(hreq)) + 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; @@ -986,12 +972,11 @@ static enum daap_reply_result daap_reply_update(struct httpd_request *hreq) { struct daap_update_request *ur; - struct event_base *evbase; const char *param; int reqd_rev; int ret; - if (!httpd_request_connection_get(hreq)) + 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; @@ -1039,9 +1024,7 @@ daap_reply_update(struct httpd_request *hreq) if (DAAP_UPDATE_REFRESH > 0) { - evbase = httpd_request_evbase_get(hreq); - - ur->timeout = evtimer_new(evbase, 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 @@ -1066,7 +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. */ - httpd_request_closecb_set(hreq, update_fail_cb, ur); + httpd_request_close_cb_set(hreq, update_fail_cb, ur); return DAAP_REPLY_NONE; } @@ -1948,7 +1931,7 @@ daap_reply_extra_data(struct httpd_request *hreq) int max_h; int ret; - if (!httpd_request_connection_get(hreq)) + 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; @@ -2028,7 +2011,7 @@ daap_stream(struct httpd_request *hreq) int id; int ret; - if (!httpd_request_connection_get(hreq)) + if (!hreq->backend) { DPRINTF(E_LOG, L_DAAP, "Bug! daap_stream() cannot be called without an actual connection\n"); return DAAP_REPLY_NO_CONNECTION; @@ -2153,7 +2136,7 @@ static struct httpd_uri_map daap_handlers[] = }, { .regexp = "^/update$", - .handler = daap_reply_update + .handler = daap_reply_update, }, { .regexp = "^/activity$", @@ -2229,8 +2212,7 @@ daap_request(struct httpd_request *hreq) if (!hreq->handler) { DPRINTF(E_LOG, L_DAAP, "Unrecognized path in DAAP request: '%s'\n", hreq->uri); - - httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request"); + daap_reply_send(hreq, DAAP_REPLY_BAD_REQUEST); return; } @@ -2273,7 +2255,7 @@ daap_request(struct httpd_request *hreq) { // The cache will return the data gzipped, so httpd_send_reply won't need to do it httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); // TODO not all want this reply + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); // TODO not all want this reply return; } @@ -2317,7 +2299,7 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri); - hreq = httpd_request_new(NULL, uri, user_agent); + hreq = httpd_request_new(NULL, NULL, uri, user_agent); if (!hreq) { DPRINTF(E_LOG, L_DAAP, "Error building request: '%s'\n", uri); @@ -2366,7 +2348,6 @@ daap_deinit(void) { struct daap_session *s; struct daap_update_request *ur; - httpd_connection *conn; for (s = daap_sessions; daap_sessions; s = daap_sessions) { @@ -2378,10 +2359,7 @@ daap_deinit(void) { update_requests = ur->next; - httpd_request_closecb_set(ur->hreq, NULL, NULL); - conn = httpd_request_connection_get(ur->hreq); - httpd_connection_free(conn); // TODO necessary? - + daap_reply_send(ur->hreq, DAAP_REPLY_SERVUNAVAIL); update_free(ur); } } diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 2eda58a7..6de4f342 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -139,25 +139,12 @@ static struct db_queue_item dummy_queue_item; static void dacp_send_error(struct httpd_request *hreq, const char *container, const char *errmsg) { - struct evbuffer *evbuf; - if (!hreq) return; - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for DMAP error\n"); + dmap_error_make(hreq->out_body, container, errmsg); - httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - return; - } - - dmap_error_make(evbuf, container, errmsg); - - httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); - - evbuffer_free(evbuf); + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); } static void @@ -640,7 +627,7 @@ dacp_request_authorize(struct httpd_request *hreq) invalid: 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, 403, "Forbidden"); + httpd_send_error(hreq, HTTP_FORBIDDEN, "Forbidden"); return -1; } @@ -719,13 +706,10 @@ playstatusupdate_cb(int fd, short what, void *arg); static struct dacp_update_request * update_request_new(struct httpd_request *hreq) { - struct event_base *evbase; struct dacp_update_request *ur; - evbase = httpd_request_evbase_get(hreq); - CHECK_NULL(L_DACP, ur = calloc(1, sizeof(struct dacp_update_request))); - CHECK_NULL(L_DACP, ur->updateev = event_new(evbase, -1, 0, playstatusupdate_cb, ur)); + CHECK_NULL(L_DACP, ur->updateev = event_new(hreq->evbase, -1, 0, playstatusupdate_cb, ur)); ur->hreq = hreq; return ur; @@ -771,40 +755,30 @@ static void playstatusupdate_cb(int fd, short what, void *arg) { struct dacp_update_request *ur = arg; - struct evbuffer *update; + struct httpd_request *hreq = ur->hreq; int ret; - CHECK_NULL(L_DACP, update = evbuffer_new()); - - ret = make_playstatusupdate(update, update_current_rev); + ret = make_playstatusupdate(hreq->out_body, update_current_rev); if (ret < 0) goto error; - httpd_request_closecb_set(ur->hreq, NULL, NULL); - - httpd_send_reply(ur->hreq, HTTP_OK, "OK", update, 0); + 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: - evbuffer_free(update); + return; } static void -update_fail_cb(httpd_connection *conn, void *arg) +update_fail_cb(struct httpd_request *hreq, void *arg) { struct dacp_update_request *ur = arg; DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n"); - httpd_request_closecb_set(ur->hreq, NULL, NULL); - - // Peer won't get this, it is just to make sure hreq and evhttp's request get - // freed - httpd_send_error(ur->hreq, HTTP_BADREQUEST, "Bad Request"); - pthread_mutex_lock(&update_request_lck); update_request_remove(&update_requests, ur); pthread_mutex_unlock(&update_request_lck); @@ -1028,7 +1002,6 @@ dacp_propset_devicebusy(const char *value, struct httpd_request *hreq) static void dacp_propset_playingtime(const char *value, struct httpd_request *hreq) { - struct event_base *evbase; struct timeval tv; int seek_target; intptr_t seek_target_packed; @@ -1047,8 +1020,7 @@ dacp_propset_playingtime(const char *value, struct httpd_request *hreq) evutil_timerclear(&tv); tv.tv_usec = 200 * 1000; - evbase = httpd_request_evbase_get(hreq); - event_base_once(evbase, -1, EV_TIMEOUT, seek_timer_cb, (void *)seek_target_packed, &tv); + event_base_once(hreq->evbase, -1, EV_TIMEOUT, seek_timer_cb, (void *)seek_target_packed, &tv); } static void @@ -1194,7 +1166,7 @@ dacp_reply_ctrlint(struct httpd_request *hreq) 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, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; } @@ -1338,7 +1310,7 @@ dacp_reply_cue_play(struct httpd_request *hreq) dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */ dmap_add_int(hreq->out_body, "miid", status.id);/* 12 */ - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; } @@ -1358,7 +1330,7 @@ dacp_reply_cue_clear(struct httpd_request *hreq) dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */ dmap_add_int(hreq->out_body, "miid", 0); /* 12 */ - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; } @@ -1407,12 +1379,12 @@ dacp_reply_play(struct httpd_request *hreq) ret = player_playback_start(); if (ret < 0) { - httpd_send_error(hreq, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1544,11 +1516,11 @@ dacp_reply_playspec(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; out_fail: - httpd_send_error(hreq, 500, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); return -1; } @@ -1565,7 +1537,7 @@ dacp_reply_stop(struct httpd_request *hreq) player_playback_stop(); /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1582,7 +1554,7 @@ dacp_reply_pause(struct httpd_request *hreq) player_playback_pause(); /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1609,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, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1634,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, 500, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); return -1; } @@ -1643,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, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1667,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, 500, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); return -1; } @@ -1676,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, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1698,7 +1670,7 @@ dacp_reply_beginff(struct httpd_request *hreq) /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1715,7 +1687,7 @@ dacp_reply_beginrew(struct httpd_request *hreq) /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1732,7 +1704,7 @@ dacp_reply_playresume(struct httpd_request *hreq) /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -1884,7 +1856,7 @@ dacp_reply_playqueuecontents(struct httpd_request *hreq) 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, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; @@ -1922,7 +1894,7 @@ dacp_reply_playqueueedit_clear(struct httpd_request *hreq) dmap_add_int(hreq->out_body, "mstt", 200); /* 12 */ dmap_add_int(hreq->out_body, "miid", 0); /* 12 */ - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; } @@ -2060,7 +2032,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2108,7 +2080,7 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2144,7 +2116,7 @@ dacp_reply_playqueueedit_remove(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2267,9 +2239,9 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) { ret = make_playstatusupdate(hreq->out_body, update_current_rev); if (ret < 0) - httpd_send_error(hreq, 500, "Internal Server Error"); + httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error"); else - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return ret; } @@ -2289,10 +2261,9 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq) 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. - */ - httpd_request_closecb_set(hreq, update_fail_cb, ur); + // 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; } @@ -2370,7 +2341,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq) snprintf(clen, sizeof(clen), "%ld", (long)len); httpd_header_add(hreq->out_headers, "Content-Length", clen); - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); return 0; no_artwork: @@ -2471,7 +2442,7 @@ dacp_reply_getproperty(struct httpd_request *hreq) evbuffer_free(proplist); - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; @@ -2526,7 +2497,7 @@ dacp_reply_setproperty(struct httpd_request *hreq) httpd_query_iterate(hreq->query, setproperty_cb, hreq); /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2554,7 +2525,7 @@ dacp_reply_getspeakers(struct httpd_request *hreq) evbuffer_free(spklist); - httpd_send_reply(hreq, HTTP_OK, "OK", hreq->out_body, 0); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); return 0; } @@ -2638,13 +2609,13 @@ dacp_reply_setspeakers(struct httpd_request *hreq) if (ret == -2) httpd_send_error(hreq, 902, ""); else - httpd_send_error(hreq, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2670,7 +2641,7 @@ dacp_reply_volumeup(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2696,7 +2667,7 @@ dacp_reply_volumedown(struct httpd_request *hreq) } /* 204 No Content is the canonical reply */ - httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2718,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, 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, HTTP_NOCONTENT, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP); return 0; } @@ -2784,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$", @@ -2879,7 +2850,6 @@ static void dacp_deinit(void) { struct dacp_update_request *ur; - httpd_connection *conn; listener_remove(dacp_playstatus_update_handler); @@ -2887,10 +2857,7 @@ dacp_deinit(void) { update_requests = ur->next; - httpd_request_closecb_set(ur->hreq, NULL, NULL); - conn = httpd_request_connection_get(ur->hreq); - httpd_connection_free(conn); // TODO necessary? - + httpd_send_error(ur->hreq, HTTP_SERVUNAVAIL, "Service Unavailable"); update_request_free(ur); } } diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 4d28fcef..9e57ce04 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -53,11 +53,11 @@ 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 void httpd_backend_data; // Not used for evhttp +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_connection_closecb)(httpd_connection *conn, void *arg); +typedef void (*httpd_close_cb)(struct httpd_request *hreq, 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); @@ -74,6 +74,15 @@ enum httpd_methods 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), @@ -94,6 +103,14 @@ enum httpd_modules 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; @@ -122,6 +139,7 @@ struct httpd_uri_map char *regexp; int (*handler)(struct httpd_request *hreq); void *preg; + int flags; // See enum httpd_handler_flags }; @@ -174,6 +192,10 @@ struct httpd_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; }; @@ -206,17 +228,16 @@ httpd_response_not_cachable(struct httpd_request *hreq); * @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 httpd_request *hreq, 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); void httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason); void -httpd_send_reply_chunk(struct httpd_request *hreq, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg); +httpd_send_reply_chunk(struct httpd_request *hreq, httpd_connection_chunkcb cb, void *arg); void httpd_send_reply_end(struct httpd_request *hreq); @@ -270,25 +291,13 @@ void httpd_headers_clear(httpd_headers *headers); void -httpd_connection_free(httpd_connection *conn); - -httpd_connection * -httpd_request_connection_get(struct httpd_request *hreq); - -void -httpd_request_backend_free(struct httpd_request *hreq); - -int -httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg); - -struct event_base * -httpd_request_evbase_get(struct httpd_request *hreq); +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, const char *uri, const char *user_agent); +httpd_request_new(httpd_backend *backend, httpd_server *server, const char *uri, const char *user_agent); void httpd_server_free(httpd_server *server); @@ -303,28 +312,20 @@ httpd_server_allow_origin_set(httpd_server *server, bool allow); /*----------------- Only called by httpd.c to send raw replies ---------------*/ void -httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf); - -void -httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason); - -void -httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg); - -void -httpd_backend_reply_end_send(httpd_backend *backend); +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_backend_data_create(httpd_backend *backend, httpd_server *server); void httpd_backend_data_free(httpd_backend_data *backend_data); -httpd_connection * -httpd_backend_connection_get(httpd_backend *backend); +struct event_base * +httpd_backend_evbase_get(httpd_backend *backend); const char * httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data); diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 7fd3afb4..cd7e319b 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4727,13 +4727,13 @@ jsonapi_request(struct httpd_request *hreq) { case HTTP_OK: /* 200 OK */ httpd_header_add(hreq->out_headers, "Content-Type", "application/json"); - httpd_send_reply(hreq, status_code, "OK", hreq->out_body, 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(hreq, status_code, "No Content", hreq->out_body, 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(hreq, 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(hreq, status_code, "Bad Request"); diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index 52b577eb..ac5a3bc4 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -32,14 +32,16 @@ #include #include +#include + #include "misc.h" #include "logger.h" +#include "commands.h" #include "httpd_internal.h" -#define DEBUG_ALLOC 1 +// #define DEBUG_ALLOC 1 #ifdef DEBUG_ALLOC -#include static pthread_mutex_t debug_alloc_lck = PTHREAD_MUTEX_INITIALIZER; static int debug_alloc_count; #endif @@ -56,10 +58,41 @@ 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) @@ -109,45 +142,27 @@ httpd_headers_clear(httpd_headers *headers) } void -httpd_connection_free(httpd_connection *conn) +httpd_request_close_cb_set(struct httpd_request *hreq, httpd_close_cb cb, void *arg) { - if (!conn) - return; + struct httpd_disconnect *disconnect = &hreq->backend_data->disconnect; - evhttp_connection_free(conn); -} + pthread_mutex_lock(&disconnect->lock); -httpd_connection * -httpd_request_connection_get(struct httpd_request *hreq) -{ - return httpd_backend_connection_get(hreq->backend); -} + disconnect->cb = cb; + disconnect->cbarg = arg; -void -httpd_request_backend_free(struct httpd_request *hreq) -{ - evhttp_request_free(hreq->backend); -} + if (hreq->is_async) + { + if (disconnect->ev) + event_free(disconnect->ev); -int -httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg) -{ - httpd_connection *conn = httpd_request_connection_get(hreq); - if (!conn) - return -1; + if (disconnect->cb) + disconnect->ev = event_new(hreq->evbase, -1, 0, closecb_worker, hreq); + else + disconnect->ev = NULL; + } - evhttp_connection_set_closecb(conn, cb, arg); - return 0; -} - -struct event_base * -httpd_request_evbase_get(struct httpd_request *hreq) -{ - httpd_connection *conn = httpd_request_connection_get(hreq); - if (!conn) - return NULL; - - return evhttp_connection_get_base(conn); + pthread_mutex_unlock(&disconnect->lock); } void @@ -172,7 +187,7 @@ httpd_request_free(struct httpd_request *hreq) } struct httpd_request * -httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agent) +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; @@ -190,7 +205,7 @@ httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agen hreq->backend = backend; if (backend) { - backend_data = httpd_backend_data_create(backend); + backend_data = httpd_backend_data_create(backend, server); hreq->backend_data = backend_data; hreq->uri = httpd_backend_uri_get(backend, backend_data); @@ -233,6 +248,51 @@ httpd_request_new(httpd_backend *backend, const char *uri, const char *user_agen return NULL; } +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(hreq, 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(hreq, disconnect->cbarg); + httpd_send_reply_end(hreq); // hreq is now deallocated +} + static void gencb_httpd(httpd_backend *backend, void *arg) { @@ -253,13 +313,17 @@ gencb_httpd(httpd_backend *backend, void *arg) if (bufev) bufferevent_enable(bufev, EV_READ); - hreq = httpd_request_new(backend, NULL, NULL); + 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); } @@ -275,6 +339,7 @@ httpd_server_free(httpd_server *server) if (server->evhttp) evhttp_free(server->evhttp); + commands_base_free(server->cmdbase); free(server); } @@ -286,6 +351,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c 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; @@ -294,7 +360,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c if (server->fd <= 0) goto error; - // Backlog of 128 is the same libevent uses + // Backlog of 128 is the same that libevent uses ret = listen(server->fd, 128); if (ret < 0) goto error; @@ -318,46 +384,109 @@ 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); } -void -httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf) +// 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) { - evhttp_send_reply(backend, code, reason, evbuf); + 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_backend_reply_start_send(httpd_backend *backend, int code, const char *reason) +httpd_send(struct httpd_request *hreq, enum httpd_reply_type type, int code, const char *reason, httpd_connection_chunkcb cb, void *cbarg) { - evhttp_send_reply_start(backend, code, reason); -} + struct httpd_server *server = hreq->backend_data->server; + struct httpd_reply reply = { + .hreq = hreq, + .type = type, + .code = code, + .chunkcb = cb, + .cbarg = cbarg, + .reason = reason, + }; -void -httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg) -{ - evhttp_send_reply_chunk_with_cb(backend, evbuf, cb, arg); -} + if (type & HTTPD_F_REPLY_LAST) + httpd_request_close_cb_set(hreq, NULL, NULL); -void -httpd_backend_reply_end_send(httpd_backend *backend) -{ - evhttp_send_reply_end(backend); + // 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_backend_data_create(httpd_backend *backend, httpd_server *server) { - return "dummy"; + 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) { - // Nothing to do + if (!backend_data) + return; + + if (backend_data->disconnect.ev) + event_free(backend_data->disconnect.ev); + + free(backend_data); } -httpd_connection * -httpd_backend_connection_get(httpd_backend *backend) +struct event_base * +httpd_backend_evbase_get(httpd_backend *backend) { - return evhttp_request_get_connection(backend); + httpd_connection *conn = evhttp_request_get_connection(backend); + if (!conn) + return NULL; + + return evhttp_connection_get_base(conn); } const char * @@ -393,7 +522,7 @@ httpd_backend_output_buffer_get(httpd_backend *backend) int httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data) { - httpd_connection *conn = httpd_backend_connection_get(backend); + httpd_connection *conn = evhttp_request_get_connection(backend); if (!conn) return -1; diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index 38702626..7bfce8f4 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -119,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)); @@ -148,21 +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 httpd_request *hreq, char *errmsg) { - struct evbuffer *evbuf; 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. @@ -185,22 +172,19 @@ rsp_send_error(struct httpd_request *hreq, 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(hreq, HTTP_SERVUNAVAIL, "Internal Server Error"); - return; } httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8"); httpd_header_add(hreq->out_headers, "Connection", "close"); - httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); - - evbuffer_free(evbuf); + httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP); } static int @@ -277,24 +261,21 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq) static void rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply) { - struct evbuffer *evbuf; + 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(hreq, "Could not finalize reply"); - return; } httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8"); httpd_header_add(hreq->out_headers, "Connection", "close"); - httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, 0); - - evbuffer_free(evbuf); + httpd_send_reply(hreq, HTTP_OK, "OK", 0); } static int @@ -844,7 +825,7 @@ static struct httpd_uri_map rsp_handlers[] = }, { .regexp = "^/rsp/stream/[[:digit:]]+$", - .handler = rsp_stream + .handler = rsp_stream, }, { .regexp = NULL, diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 001899d6..16bf329f 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -192,27 +192,15 @@ session_new(struct httpd_request *hreq, bool icy_is_requested) return session; } -static void -session_end(struct streaming_session *session) -{ - DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); - - // 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. This also makes - // sure the hreq gets freed. - httpd_send_reply_end(session->hreq); - session_free(session); -} - /* ----------------------------- Event callbacks ---------------------------- */ static void -conn_close_cb(httpd_connection *conn, void *arg) +conn_close_cb(struct httpd_request *hreq, void *arg) { struct streaming_session *session = arg; - session_end(session); + session_free(session); } static void @@ -227,8 +215,10 @@ read_cb(evutil_socket_t fd, short event, void *arg) len = evbuffer_read(session->readbuf, fd, -1); if (len < 0 && errno != EAGAIN) { - httpd_request_closecb_set(hreq, NULL, NULL); - session_end(session); + DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port); + + httpd_send_reply_end(session->hreq); + session_free(session); return; } @@ -237,7 +227,7 @@ read_cb(evutil_socket_t fd, short event, void *arg) else evbuffer_add_buffer(hreq->out_body, session->readbuf); - httpd_send_reply_chunk(hreq, hreq->out_body, NULL, NULL); + httpd_send_reply_chunk(hreq, NULL, NULL); session->bytes_sent += len; } @@ -249,7 +239,6 @@ static int streaming_mp3_handler(struct httpd_request *hreq) { struct streaming_session *session; - struct event_base *evbase; const char *name = cfg_getstr(cfg_getsec(cfg, "library"), "name"); const char *param; bool icy_is_requested; @@ -271,11 +260,10 @@ streaming_mp3_handler(struct httpd_request *hreq) // Ask streaming output module for a fd to read mp3 from session->fd = streaming_session_register(STREAMING_FORMAT_MP3, streaming_default_quality); - CHECK_NULL(L_STREAMING, evbase = httpd_request_evbase_get(hreq)); - CHECK_NULL(L_STREAMING, session->readev = event_new(evbase, session->fd, EV_READ | EV_PERSIST, read_cb, session)); + CHECK_NULL(L_STREAMING, session->readev = event_new(hreq->evbase, session->fd, EV_READ | EV_PERSIST, read_cb, session)); event_add(session->readev, NULL); - httpd_request_closecb_set(hreq, conn_close_cb, session); + httpd_request_close_cb_set(hreq, conn_close_cb, session); httpd_header_add(hreq->out_headers, "Content-Type", "audio/mpeg"); httpd_header_add(hreq->out_headers, "Server", PACKAGE_NAME "/" VERSION); @@ -292,7 +280,8 @@ static struct httpd_uri_map streaming_handlers[] = { { .regexp = "^/stream.mp3$", - .handler = streaming_mp3_handler + .handler = streaming_mp3_handler, + .flags = HTTPD_HANDLER_REALTIME, }, { .regexp = NULL, diff --git a/src/worker.c b/src/worker.c index 9244a8bf..ecbc9836 100644 --- a/src/worker.c +++ b/src/worker.c @@ -43,10 +43,10 @@ #include "evthr.h" #include "misc.h" -#define THREADPOOL_NTHREADS 2 +#define THREADPOOL_NTHREADS 4 static struct evthr_pool *worker_threadpool; - +static __thread struct evthr *worker_thr; /* ----------------------------- CALLBACK EXECUTION ------------------------- */ @@ -98,12 +98,16 @@ init_cb(struct evthr *thr, void *shared) { CHECK_ERR(L_MAIN, db_perthread_init()); + worker_thr = thr; + thread_setname(pthread_self(), "worker"); } static void exit_cb(struct evthr *thr, void *shared) { + worker_thr = NULL; + db_perthread_deinit(); } @@ -145,6 +149,12 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay) evthr_pool_defer(worker_threadpool, execute, cmdarg); } +struct event_base * +worker_evbase_get(void) +{ + return evthr_get_base(worker_thr); +} + int worker_init(void) { 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); From e94838925e6fc861e651ff7ef7c9d95073205824 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 8 Feb 2023 18:29:48 +0100 Subject: [PATCH 21/21] [httpd] Change prototype for the close connection callback Don't include hreq since it isn't fully valid, so caller shouldn't be invited to dereference it. --- src/httpd.c | 2 +- src/httpd_daap.c | 2 +- src/httpd_dacp.c | 2 +- src/httpd_internal.h | 2 +- src/httpd_libevhttp.c | 7 +++++-- src/httpd_streaming.c | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index 4d1b241e..8a5087bf 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -837,7 +837,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) } static void -stream_fail_cb(struct httpd_request *hreq, void *arg) +stream_fail_cb(void *arg) { struct stream_ctx *st = arg; diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 800283ae..5fed4db9 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -300,7 +300,7 @@ update_refresh_cb(int fd, short event, void *arg) } static void -update_fail_cb(struct httpd_request *hreq, void *arg) +update_fail_cb(void *arg) { struct daap_update_request *ur = arg; diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 6de4f342..f8ec8c9d 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -773,7 +773,7 @@ playstatusupdate_cb(int fd, short what, void *arg) } static void -update_fail_cb(struct httpd_request *hreq, void *arg) +update_fail_cb(void *arg) { struct dacp_update_request *ur = arg; diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 9e57ce04..0157601e 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -57,7 +57,7 @@ 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)(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); diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index ac5a3bc4..6949df8a 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -248,6 +248,9 @@ httpd_request_new(httpd_backend *backend, httpd_server *server, const char *uri, 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) { @@ -257,7 +260,7 @@ closecb_worker(evutil_socket_t fd, short event, void *arg) pthread_mutex_lock(&disconnect->lock); if (disconnect->cb) - disconnect->cb(hreq, disconnect->cbarg); + disconnect->cb(disconnect->cbarg); pthread_mutex_unlock(&disconnect->lock); @@ -289,7 +292,7 @@ closecb_httpd(httpd_connection *conn, void *arg) if (!disconnect->cb) return; - disconnect->cb(hreq, disconnect->cbarg); + disconnect->cb(disconnect->cbarg); httpd_send_reply_end(hreq); // hreq is now deallocated } diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 16bf329f..1306e390 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -196,7 +196,7 @@ session_new(struct httpd_request *hreq, bool icy_is_requested) /* ----------------------------- Event callbacks ---------------------------- */ static void -conn_close_cb(struct httpd_request *hreq, void *arg) +conn_close_cb(void *arg) { struct streaming_session *session = arg;