mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-05 10:48:09 -05:00
[httpd/jsonapi] Add cache control headers to some json api endpoints
Adds utility functions to httpd.c for checking the request headers for either an "If-None-Match" or an "If-Not-Modified-Since" headers. If the header value is found and it matches the current value for the requested resource, we return early with a http response code 403 (Not Modified). If the request header value is not present or does not match we add the current ETag/Last-Modified values to the response headers and process the request normally.
This commit is contained in:
parent
99a812ad9a
commit
d15018cb99
85
src/httpd.c
85
src/httpd.c
@ -268,6 +268,75 @@ httpd_redirect_to_admin(struct evhttp_request *req)
|
|||||||
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
|
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if the given ETag matches the "If-None-Match" request header
|
||||||
|
*
|
||||||
|
* If the request does not contains a "If-None-Match" header value or the value
|
||||||
|
* does not match the given ETag, it returns false (modified) and adds the
|
||||||
|
* "Cache-Control" and "ETag" headers to the response header.
|
||||||
|
*
|
||||||
|
* @param req The request with request and response headers
|
||||||
|
* @param etag The valid ETag for the requested resource
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
evhttp_add_header(output_headers, "ETag", etag);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if the given timestamp matches the "If-Modified-Since" request header
|
||||||
|
*
|
||||||
|
* If the request does not contains a "If-Modified-Since" header value or the value
|
||||||
|
* does not match the given timestamp, it returns false (modified) and adds the
|
||||||
|
* "Cache-Control" and "Last-Modified" headers to the response header.
|
||||||
|
*
|
||||||
|
* @param req The request with request and response headers
|
||||||
|
* @param mtime The last modified timestamp for the requested resource
|
||||||
|
* @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, const time_t *mtime)
|
||||||
|
{
|
||||||
|
struct evkeyvalq *input_headers;
|
||||||
|
struct evkeyvalq *output_headers;
|
||||||
|
char last_modified[1000];
|
||||||
|
const char *modified_since;
|
||||||
|
|
||||||
|
input_headers = evhttp_request_get_input_headers(req);
|
||||||
|
modified_since = evhttp_find_header(input_headers, "If-Modified-Since");
|
||||||
|
|
||||||
|
strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", gmtime(mtime));
|
||||||
|
|
||||||
|
// Return not modified, if given timestamp matches "If-Modified-Since" request header
|
||||||
|
if (modified_since && (strcasecmp(last_modified, modified_since) == 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");
|
||||||
|
evhttp_add_header(output_headers, "Last-Modified", last_modified);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
serve_file(struct evhttp_request *req, const char *uri)
|
serve_file(struct evhttp_request *req, const char *uri)
|
||||||
{
|
{
|
||||||
@ -276,15 +345,11 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
char deref[PATH_MAX];
|
char deref[PATH_MAX];
|
||||||
char *ctype;
|
char *ctype;
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
struct evkeyvalq *input_headers;
|
|
||||||
struct evkeyvalq *output_headers;
|
struct evkeyvalq *output_headers;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
int fd;
|
int fd;
|
||||||
int i;
|
int i;
|
||||||
uint8_t buf[4096];
|
uint8_t buf[4096];
|
||||||
const char *modified_since;
|
|
||||||
char last_modified[1000];
|
|
||||||
struct tm *tm_modified;
|
|
||||||
bool slashed;
|
bool slashed;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -384,13 +449,7 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tm_modified = gmtime(&sb.st_mtime);
|
if (httpd_request_not_modified_since(req, &sb.st_mtime))
|
||||||
strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", tm_modified);
|
|
||||||
|
|
||||||
input_headers = evhttp_request_get_input_headers(req);
|
|
||||||
modified_since = evhttp_find_header(input_headers, "If-Modified-Since");
|
|
||||||
|
|
||||||
if (modified_since && strcasecmp(last_modified, modified_since) == 0)
|
|
||||||
{
|
{
|
||||||
httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
|
httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
|
||||||
return;
|
return;
|
||||||
@ -448,10 +507,6 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
output_headers = evhttp_request_get_output_headers(req);
|
output_headers = evhttp_request_get_output_headers(req);
|
||||||
evhttp_add_header(output_headers, "Content-Type", ctype);
|
evhttp_add_header(output_headers, "Content-Type", ctype);
|
||||||
|
|
||||||
// Allow browsers to cache the file
|
|
||||||
evhttp_add_header(output_headers, "Cache-Control", "private");
|
|
||||||
evhttp_add_header(output_headers, "Last-Modified", last_modified);
|
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
|
||||||
|
|
||||||
evbuffer_free(evbuf);
|
evbuffer_free(evbuf);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
|
#include <time.h>
|
||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
#include <event2/keyvalq_struct.h>
|
#include <event2/keyvalq_struct.h>
|
||||||
@ -100,6 +101,12 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
|
|||||||
void
|
void
|
||||||
httpd_stream_file(struct evhttp_request *req, int id);
|
httpd_stream_file(struct evhttp_request *req, int id);
|
||||||
|
|
||||||
|
bool
|
||||||
|
httpd_request_not_modified_since(struct evhttp_request *req, const time_t *mtime);
|
||||||
|
|
||||||
|
bool
|
||||||
|
httpd_request_etag_matches(struct evhttp_request *req, const char *etag);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gzips an evbuffer
|
* Gzips an evbuffer
|
||||||
*
|
*
|
||||||
|
@ -1706,6 +1706,7 @@ jsonapi_reply_queue(struct httpd_request *hreq)
|
|||||||
int start_pos, end_pos;
|
int start_pos, end_pos;
|
||||||
int version;
|
int version;
|
||||||
int count;
|
int count;
|
||||||
|
char etag[21];
|
||||||
struct player_status status;
|
struct player_status status;
|
||||||
struct db_queue_item queue_item;
|
struct db_queue_item queue_item;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
@ -1713,12 +1714,16 @@ jsonapi_reply_queue(struct httpd_request *hreq)
|
|||||||
json_object *item;
|
json_object *item;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
memset(&query_params, 0, sizeof(struct query_params));
|
|
||||||
reply = json_object_new_object();
|
|
||||||
|
|
||||||
version = db_admin_getint(DB_ADMIN_QUEUE_VERSION);
|
version = db_admin_getint(DB_ADMIN_QUEUE_VERSION);
|
||||||
count = db_queue_get_count();
|
count = db_queue_get_count();
|
||||||
|
|
||||||
|
snprintf(etag, sizeof(etag), "%d", version);
|
||||||
|
if (httpd_request_etag_matches(hreq->req, etag))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
memset(&query_params, 0, sizeof(struct query_params));
|
||||||
|
reply = json_object_new_object();
|
||||||
|
|
||||||
json_object_object_add(reply, "version", json_object_new_int(version));
|
json_object_object_add(reply, "version", json_object_new_int(version));
|
||||||
json_object_object_add(reply, "count", json_object_new_int(count));
|
json_object_object_add(reply, "count", json_object_new_int(count));
|
||||||
|
|
||||||
@ -1880,6 +1885,7 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_artists(struct httpd_request *hreq)
|
jsonapi_reply_library_artists(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
const char *param;
|
const char *param;
|
||||||
enum media_kind media_kind;
|
enum media_kind media_kind;
|
||||||
@ -1888,6 +1894,11 @@ jsonapi_reply_library_artists(struct httpd_request *hreq)
|
|||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
media_kind = 0;
|
media_kind = 0;
|
||||||
param = evhttp_find_header(hreq->query, "media_kind");
|
param = evhttp_find_header(hreq->query, "media_kind");
|
||||||
if (param)
|
if (param)
|
||||||
@ -1940,10 +1951,16 @@ jsonapi_reply_library_artists(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_artist(struct httpd_request *hreq)
|
jsonapi_reply_library_artist(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
const char *artist_id;
|
const char *artist_id;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
artist_id = hreq->uri_parsed->path_parts[3];
|
artist_id = hreq->uri_parsed->path_parts[3];
|
||||||
|
|
||||||
reply = fetch_artist(artist_id);
|
reply = fetch_artist(artist_id);
|
||||||
@ -1969,6 +1986,7 @@ jsonapi_reply_library_artist(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
|
jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
const char *artist_id;
|
const char *artist_id;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
@ -1976,6 +1994,11 @@ jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
|
|||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
artist_id = hreq->uri_parsed->path_parts[3];
|
artist_id = hreq->uri_parsed->path_parts[3];
|
||||||
|
|
||||||
reply = json_object_new_object();
|
reply = json_object_new_object();
|
||||||
@ -2018,6 +2041,7 @@ jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_albums(struct httpd_request *hreq)
|
jsonapi_reply_library_albums(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
const char *param;
|
const char *param;
|
||||||
enum media_kind media_kind;
|
enum media_kind media_kind;
|
||||||
@ -2026,6 +2050,11 @@ jsonapi_reply_library_albums(struct httpd_request *hreq)
|
|||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
media_kind = 0;
|
media_kind = 0;
|
||||||
param = evhttp_find_header(hreq->query, "media_kind");
|
param = evhttp_find_header(hreq->query, "media_kind");
|
||||||
if (param)
|
if (param)
|
||||||
@ -2078,10 +2107,16 @@ jsonapi_reply_library_albums(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_album(struct httpd_request *hreq)
|
jsonapi_reply_library_album(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
const char *album_id;
|
const char *album_id;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
album_id = hreq->uri_parsed->path_parts[3];
|
album_id = hreq->uri_parsed->path_parts[3];
|
||||||
|
|
||||||
reply = fetch_album(album_id);
|
reply = fetch_album(album_id);
|
||||||
@ -2107,6 +2142,7 @@ jsonapi_reply_library_album(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
|
jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
const char *album_id;
|
const char *album_id;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
@ -2114,6 +2150,11 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
|
|||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
album_id = hreq->uri_parsed->path_parts[3];
|
album_id = hreq->uri_parsed->path_parts[3];
|
||||||
|
|
||||||
reply = json_object_new_object();
|
reply = json_object_new_object();
|
||||||
@ -2156,12 +2197,18 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_playlists(struct httpd_request *hreq)
|
jsonapi_reply_library_playlists(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
json_object *items;
|
json_object *items;
|
||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
reply = json_object_new_object();
|
reply = json_object_new_object();
|
||||||
items = json_object_new_array();
|
items = json_object_new_array();
|
||||||
json_object_object_add(reply, "items", items);
|
json_object_object_add(reply, "items", items);
|
||||||
@ -2202,10 +2249,16 @@ jsonapi_reply_library_playlists(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_playlist(struct httpd_request *hreq)
|
jsonapi_reply_library_playlist(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
const char *playlist_id;
|
const char *playlist_id;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
playlist_id = hreq->uri_parsed->path_parts[3];
|
playlist_id = hreq->uri_parsed->path_parts[3];
|
||||||
|
|
||||||
reply = fetch_playlist(playlist_id);
|
reply = fetch_playlist(playlist_id);
|
||||||
@ -2231,6 +2284,7 @@ jsonapi_reply_library_playlist(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
|
jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
|
time_t db_update;
|
||||||
struct query_params query_params;
|
struct query_params query_params;
|
||||||
json_object *reply;
|
json_object *reply;
|
||||||
json_object *items;
|
json_object *items;
|
||||||
@ -2238,6 +2292,11 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
|
|||||||
int total;
|
int total;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
db_update = (time_t) db_admin_getint64(DB_ADMIN_START_TIME);
|
||||||
|
if (db_update && httpd_request_not_modified_since(hreq->req, &db_update))
|
||||||
|
return HTTP_NOTMODIFIED;
|
||||||
|
|
||||||
|
|
||||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -2666,6 +2725,9 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
|||||||
case HTTP_NOCONTENT: /* 204 No Content */
|
case HTTP_NOCONTENT: /* 204 No Content */
|
||||||
httpd_send_reply(req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
|
httpd_send_reply(req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
|
||||||
break;
|
break;
|
||||||
|
case HTTP_NOTMODIFIED: /* 304 Not Modified */
|
||||||
|
httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
|
||||||
|
break;
|
||||||
|
|
||||||
case HTTP_BADREQUEST: /* 400 Bad Request */
|
case HTTP_BADREQUEST: /* 400 Bad Request */
|
||||||
httpd_send_error(req, status_code, "Bad Request");
|
httpd_send_error(req, status_code, "Bad Request");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user