diff --git a/ChangeLog b/ChangeLog index 93d03f45b..ee0cae82e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +Mon Apr 17 01:29:07 CEST 2017 + Add support for HTTP body compression (#4982). -CG + Mon Mar 20 04:37:46 CET 2017 Implemented first working version of taler-auditor. -CG diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index ecb774b29..f936c3a48 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -55,6 +55,7 @@ taler_exchange_httpd_LDADD = \ -lgnunetutil \ -lgnunetjson \ -ljansson \ + -lz \ -lpthread if HAVE_DEVELOPER diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c index 95ddd04bf..057f89ce3 100644 --- a/src/exchange/taler-exchange-httpd_keystate.c +++ b/src/exchange/taler-exchange-httpd_keystate.c @@ -71,6 +71,16 @@ struct TEH_KS_StateHandle */ char *keys_json; + /** + * deflate-compressed version of @e keys_json, or NULL if not available. + */ + void *keys_jsonz; + + /** + * Number of bytes in @e keys_jsonz. + */ + size_t keys_jsonz_size; + /** * Mapping from denomination keys to denomination key issue struct. * Used to lookup the key by hash. @@ -709,6 +719,7 @@ ks_release_ (struct TEH_KS_StateHandle *key_state) key_state->revoked_map = NULL; } GNUNET_free_non_null (key_state->keys_json); + GNUNET_free_non_null (key_state->keys_jsonz); GNUNET_free (key_state); } } @@ -851,6 +862,17 @@ TEH_KS_acquire_ (const char *location) JSON_INDENT (2)); GNUNET_assert (NULL != key_state->keys_json); json_decref (keys); + /* also compute compressed version of /keys */ + key_state->keys_jsonz = GNUNET_strdup (key_state->keys_json); + key_state->keys_jsonz_size = strlen (key_state->keys_json); + if (MHD_YES != + TEH_RESPONSE_body_compress (&key_state->keys_jsonz, + &key_state->keys_jsonz_size)) + { + GNUNET_free (key_state->keys_jsonz); + key_state->keys_jsonz = NULL; + key_state->keys_jsonz_size = 0; + } internal_key_state = key_state; } key_state = internal_key_state; @@ -1225,10 +1247,26 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh, struct MHD_Response *response; int ret; char dat[128]; + char *json; + size_t json_len; + int comp; key_state = TEH_KS_acquire (); - response = MHD_create_response_from_buffer (strlen (key_state->keys_json), - key_state->keys_json, + comp = MHD_NO; + if (NULL != key_state->keys_jsonz) + comp = TEH_RESPONSE_can_compress (connection); + if (MHD_YES == comp) + { + json = key_state->keys_jsonz; + json_len = key_state->keys_jsonz_size; + } + else + { + json = key_state->keys_json; + json_len = strlen (key_state->keys_json); + } + response = MHD_create_response_from_buffer (json_len, + json, MHD_RESPMEM_MUST_COPY); TEH_KS_release (key_state); if (NULL == response) @@ -1241,6 +1279,15 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh, MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, rh->mime_type)); + if (MHD_YES != + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } get_date_string (key_state->reload_time, dat); GNUNET_break (MHD_YES == diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index eea534195..c67c885ec 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -23,6 +23,7 @@ * @author Christian Grothoff */ #include "platform.h" +#include #include "taler-exchange-httpd_responses.h" #include "taler_util.h" #include "taler_json_lib.h" @@ -47,6 +48,74 @@ TEH_RESPONSE_add_global_headers (struct MHD_Response *response) } +/** + * Is HTTP body deflate compression supported by the client? + * + * @param connection connection to check + * @return #MHD_YES if 'deflate' compression is allowed + */ +int +TEH_RESPONSE_can_compress (struct MHD_Connection *connection) +{ + const char *ae; + const char *de; + + ae = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_ENCODING); + if (NULL == ae) + return MHD_NO; + de = strstr (ae, + "deflate"); + if (NULL == de) + return MHD_NO; + if ( ( (de == ae) || + ( de[-1] == ',') || + (de[-1] == ' ') ) && + ( (de[strlen ("deflate")] == '\0') || + (de[strlen ("deflate")] == ',') ) ) + return MHD_YES; + return MHD_NO; +} + + +/** + * Try to compress a response body. Updates @a buf and @buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return #MHD_TES if @a buf was compressed + */ +int +TEH_RESPONSE_body_compress (void **buf, + size_t *buf_size) +{ + Bytef *cbuf; + uLongf cbuf_size; + int ret; + + cbuf_size = compressBound (*buf_size); + cbuf = malloc (cbuf_size); + if (NULL == cbuf) + return MHD_NO; + ret = compress (cbuf, + &cbuf_size, + (const Bytef *) *buf, + *buf_size); + if ( (Z_OK != ret) || + (cbuf_size >= *buf_size) ) + { + /* compression failed */ + free (cbuf); + return MHD_NO; + } + free (*buf); + *buf = (void *) cbuf; + *buf_size = (size_t) cbuf_size; + return MHD_YES; +} + + /** * Send JSON object as response. * @@ -61,12 +130,26 @@ TEH_RESPONSE_reply_json (struct MHD_Connection *connection, unsigned int response_code) { struct MHD_Response *resp; - char *json_str; + void *json_str; + size_t json_len; int ret; + int comp; - json_str = json_dumps (json, JSON_INDENT(2)); - GNUNET_assert (NULL != json_str); - resp = MHD_create_response_from_buffer (strlen (json_str), + json_str = json_dumps (json, + JSON_INDENT(2)); + if (NULL == json_str) + { + GNUNET_break (0); + return MHD_NO; + } + json_len = strlen (json_str); + /* try to compress the body */ + comp = MHD_NO; + if (MHD_YES == + TEH_RESPONSE_can_compress (connection)) + comp = TEH_RESPONSE_body_compress (&json_str, + &json_len); + resp = MHD_create_response_from_buffer (json_len, json_str, MHD_RESPMEM_MUST_FREE); if (NULL == resp) @@ -79,6 +162,19 @@ TEH_RESPONSE_reply_json (struct MHD_Connection *connection, (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); + if (MHD_YES == comp) + { + /* Need to indicate to client that body is compressed */ + if (MHD_NO == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (resp); + return MHD_NO; + } + } ret = MHD_queue_response (connection, response_code, resp); diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 83dafdca3..091d43862 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -33,6 +33,7 @@ #include "taler-exchange-httpd.h" #include "taler-exchange-httpd_db.h" + /** * Add headers we want to return in every response. * Useful for testing, like if we want to always close @@ -44,6 +45,28 @@ void TEH_RESPONSE_add_global_headers (struct MHD_Response *response); +/** + * Try to compress a response body. Updates @a buf and @buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return #MHD_TES if @a buf was compressed + */ +int +TEH_RESPONSE_body_compress (void **buf, + size_t *buf_size); + + +/** + * Is HTTP body deflate compression supported by the client? + * + * @param connection connection to check + * @return #MHD_YES if 'deflate' compression is allowed + */ +int +TEH_RESPONSE_can_compress (struct MHD_Connection *connection); + + /** * Send JSON object as response. *