From b638df81fa22225a61cc7f0551b2d4eb3100c89d Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 10 Oct 2017 16:21:47 +0200 Subject: [PATCH] Implement response to bytes range request. Handle bytes range request http header. Do not read the full buffer but try to stream it as far as possible. --- src/server/kiwix-serve.cpp | 101 +++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/src/server/kiwix-serve.cpp b/src/server/kiwix-serve.cpp index 6d2872f..a384bcb 100644 --- a/src/server/kiwix-serve.cpp +++ b/src/server/kiwix-serve.cpp @@ -300,41 +300,69 @@ static struct MHD_Response* build_404(RequestContext* request_context) { content.data(), content.size(), "", mimeType, deflated, false); } +struct RunningResponse { + zim::Article* article; + int range_start; + + RunningResponse(zim::Article* article, + int range_start) : + article(article), + range_start(range_start) + {} + + ~RunningResponse() { + delete article; + } +}; + -ssize_t callback_reader_from_blob(void* cls, +ssize_t callback_reader_from_article(void* cls, uint64_t pos, char* buf, size_t max) { - zim::Blob* blob = static_cast(cls); - size_t max_size_to_set = min(max, blob->size() - pos); + RunningResponse* response = static_cast(cls); + + size_t max_size_to_set = min( + max, + response->article->getArticleSize() - pos - response->range_start); if (max_size_to_set <= 0) { return MHD_CONTENT_READER_END_WITH_ERROR; } - memcpy(buf, blob->data() + pos, max_size_to_set); + zim::Blob blob = response->article->getData(response->range_start+pos, max_size_to_set); + memcpy(buf, blob.data(), max_size_to_set); return max_size_to_set; } -void callback_free_blob(void* cls) +void callback_free_article(void* cls) { - zim::Blob* blob = static_cast(cls); - delete blob; + RunningResponse* response = static_cast(cls); + delete response; } -static struct MHD_Response* build_callback_response_from_blob( - zim::Blob& blob, const std::string& mimeType) +static struct MHD_Response* build_callback_response_from_article( + zim::Article& article, int range_start, int range_len, const std::string& mimeType) { - zim::Blob* p_blob = new zim::Blob(blob); + RunningResponse* run_response = + new RunningResponse(new zim::Article(article), range_start); + struct MHD_Response* response - = MHD_create_response_from_callback(blob.size(), + = MHD_create_response_from_callback(article.getArticleSize(), 16384, - callback_reader_from_blob, - p_blob, - callback_free_blob); + callback_reader_from_article, + run_response, + callback_free_article); /* Tell the client that byte ranges are accepted */ MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes"); + std::ostringstream oss; + oss << "bytes " << range_start << "-" << range_start + range_len - 1 << "/" << article.getArticleSize(); + + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str()); + + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_LENGTH, + std::to_string(range_len).c_str()); /* Specify the mime type */ MHD_add_response_header( @@ -537,11 +565,10 @@ static struct MHD_Response* handle_content(RequestContext* request_context) printf("mimeType: %s\n", mimeType.c_str()); } - zim::Blob raw_content = article.getData(); - if (mimeType.find("text/") != string::npos || mimeType.find("application/javascript") != string::npos || mimeType.find("application/json") != string::npos) { + zim::Blob raw_content = article.getData(); content = string(raw_content.data(), raw_content.size()); /* Special rewrite URL in case of ZIM file use intern *asbolute* url like @@ -575,7 +602,17 @@ static struct MHD_Response* handle_content(RequestContext* request_context) return build_response( content.data(), content.size(), "", mimeType, deflated, true); } else { - return build_callback_response_from_blob(raw_content, mimeType); + int range_len; + if (request_context->range_end == -1) { + range_len = article.getArticleSize() - request_context->range_start; + } else { + range_len = request_context->range_end - request_context->range_start; + } + return build_callback_response_from_article( + article, + request_context->range_start, + range_len, + mimeType); } } @@ -626,14 +663,38 @@ static int accessHandlerCallback(void* cls, = acceptEncodingHeaderValue && string(acceptEncodingHeaderValue).find("deflate") != string::npos; - /* Check if range is requested. [TODO] Handle this somehow */ + /* Check if range is requested. */ const char* acceptRangeHeaderValue = MHD_lookup_connection_value( connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE); - const bool acceptRange = acceptRangeHeaderValue != NULL; + bool acceptRange = false; + int range_start = 0; + int range_end = -1; + if (acceptRangeHeaderValue != NULL) { + auto range = std::string(acceptRangeHeaderValue); + if (range.substr(0, 6) == "bytes=") + { + range = range.substr(6); + std::istringstream iss(range); + iss >> range_start; + range = range.substr(iss.tellg()); + if (range[0] == '-') { + range = range.substr(1); + if (! range.empty()) { + std::istringstream iss(range); + iss >> range_end; + range = range.substr(iss.tellg()); + } + if (range.empty()) { + // Nothing left to read. We are OK. + acceptRange = true; + } + } + } + } /* Prepare the variables */ struct MHD_Response* response; - int httpResponseCode = MHD_HTTP_OK; + int httpResponseCode = acceptRange ? MHD_HTTP_PARTIAL_CONTENT : MHD_HTTP_OK; std::string urlStr = string(url); /* Get searcher and reader */