mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-08-03 10:16:03 -04:00

This code is mainly copied from kiwix-tools. But : - Move all the response thing in a new class Response. - This Response class is responsible to handle all the MHD_response configuration. This way the server handle a global object and do no call to MHD_response* - Server uses a lot more the templating system with mustache. There are still few regex operations (because we need to change a content already existing). - By default, the server serves the content using the id as name. - Server creates a new Searcher per request. This way, we don't have to protect the search for multi-thread and we can do several search in the same time. - search results are not cached, this will allow future improvement in the search algorithm. - the home page is not cached. - Few more verbose information (number of request served, time spend to respond to a request). TOOD: - Readd interface selection. - Do Android wrapper. - Remove KiwixServer (who use a external process). -
251 lines
7.5 KiB
C++
251 lines
7.5 KiB
C++
|
|
|
|
|
|
#include "response.h"
|
|
#include "request_context.h"
|
|
#include "kiwixlib-resources.h"
|
|
|
|
#include "tools/regexTools.h"
|
|
#include "tools/stringTools.h"
|
|
|
|
#include "string.h"
|
|
#include <mustache.hpp>
|
|
#include <zlib.h>
|
|
|
|
|
|
#define KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE 100
|
|
|
|
namespace kiwix {
|
|
|
|
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton)
|
|
: m_verbose(verbose),
|
|
m_root(root),
|
|
m_content(""),
|
|
m_mimeType(""),
|
|
m_returnCode(MHD_HTTP_OK),
|
|
m_withTaskbar(withTaskbar),
|
|
m_withLibraryButton(withLibraryButton),
|
|
m_useCache(false),
|
|
m_addTaskbar(false),
|
|
m_bookName(""),
|
|
m_startRange(0),
|
|
m_lenRange(0)
|
|
{
|
|
}
|
|
|
|
|
|
static int print_key_value (void *cls, enum MHD_ValueKind kind,
|
|
const char *key, const char *value)
|
|
{
|
|
printf (" - %s: '%s'\n", key, value);
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
struct RunningResponse {
|
|
kiwix::Entry entry;
|
|
int range_start;
|
|
|
|
RunningResponse(kiwix::Entry entry,
|
|
int range_start) :
|
|
entry(entry),
|
|
range_start(range_start)
|
|
{}
|
|
};
|
|
|
|
static ssize_t callback_reader_from_entry(void* cls,
|
|
uint64_t pos,
|
|
char* buf,
|
|
size_t max)
|
|
{
|
|
RunningResponse* response = static_cast<RunningResponse*>(cls);
|
|
|
|
size_t max_size_to_set = min<size_t>(
|
|
max,
|
|
response->entry.getSize() - pos - response->range_start);
|
|
|
|
if (max_size_to_set <= 0) {
|
|
return MHD_CONTENT_READER_END_WITH_ERROR;
|
|
}
|
|
|
|
zim::Blob blob = response->entry.getBlob(response->range_start+pos, max_size_to_set);
|
|
memcpy(buf, blob.data(), max_size_to_set);
|
|
return max_size_to_set;
|
|
}
|
|
|
|
static void callback_free_response(void* cls)
|
|
{
|
|
RunningResponse* response = static_cast<RunningResponse*>(cls);
|
|
delete response;
|
|
}
|
|
|
|
|
|
|
|
void print_response_info(int retCode, MHD_Response* response)
|
|
{
|
|
printf("Response :\n");
|
|
printf("httpResponseCode : %d\n", retCode);
|
|
printf("headers :\n");
|
|
MHD_get_response_headers(response, print_key_value, nullptr);
|
|
}
|
|
|
|
|
|
std::string render_template(const std::string& template_str, kainjow::mustache::data data)
|
|
{
|
|
kainjow::mustache::mustache tmpl(template_str);
|
|
kainjow::mustache::data urlencode{kainjow::mustache::lambda2{
|
|
[](const std::string& str,const kainjow::mustache::renderer& r) { return urlEncode(r(str), true); }}};
|
|
data.set("urlencoded", urlencode);
|
|
std::stringstream ss;
|
|
tmpl.render(data, [&ss](const std::string& str) { ss << str; });
|
|
return ss.str();
|
|
}
|
|
|
|
|
|
void Response::introduce_taskbar()
|
|
{
|
|
if (! m_withTaskbar)
|
|
// Taskbar is globally disabled.
|
|
return;
|
|
kainjow::mustache::data data;
|
|
data.set("root", m_root);
|
|
data.set("content", m_bookName);
|
|
data.set("hascontent", !m_bookName.empty());
|
|
data.set("title", m_bookTitle);
|
|
data.set("withlibrarybutton", m_withLibraryButton);
|
|
auto head_content = render_template(RESOURCE::templates::head_part_html, data);
|
|
m_content = appendToFirstOccurence(
|
|
m_content,
|
|
"<head>",
|
|
head_content);
|
|
|
|
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
|
|
m_content = appendToFirstOccurence(
|
|
m_content,
|
|
"<body[^>]*>",
|
|
taskbar_part);
|
|
}
|
|
|
|
|
|
|
|
int Response::send(const RequestContext& request, MHD_Connection* connection)
|
|
{
|
|
MHD_Response* response = nullptr;
|
|
switch (m_mode) {
|
|
case ResponseMode::RAW_CONTENT : {
|
|
if (m_addTaskbar) {
|
|
introduce_taskbar();
|
|
}
|
|
|
|
bool shouldCompress = m_compress && request.can_compress();
|
|
shouldCompress &= m_mimeType.find("text/") != string::npos
|
|
|| m_mimeType.find("application/javascript") != string::npos
|
|
|| m_mimeType.find("application/json") != string::npos;
|
|
|
|
shouldCompress &= (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE);
|
|
|
|
if (shouldCompress) {
|
|
std::vector<Bytef> compr_buffer(compressBound(m_content.size()));
|
|
uLongf comprLen = compr_buffer.capacity();
|
|
int err = compress(&compr_buffer[0],
|
|
&comprLen,
|
|
(const Bytef*)(m_content.data()),
|
|
m_content.size());
|
|
if (err == Z_OK && comprLen > 2 && comprLen < (m_content.size() + 2)) {
|
|
/* /!\ Internet Explorer has a bug with deflate compression.
|
|
It can not handle the first two bytes (compression headers)
|
|
We need to chunk them off (move the content 2bytes)
|
|
It has no incidence on other browsers
|
|
See http://www.subbu.org/blog/2008/03/ie7-deflate-or-not and comments */
|
|
m_content = string((char*)&compr_buffer[2], comprLen - 2);
|
|
} else {
|
|
shouldCompress = false;
|
|
}
|
|
}
|
|
|
|
response = MHD_create_response_from_buffer(
|
|
m_content.size(), const_cast<char*>(m_content.data()), MHD_RESPMEM_MUST_COPY);
|
|
|
|
if (shouldCompress) {
|
|
MHD_add_response_header(
|
|
response, MHD_HTTP_HEADER_VARY, "Accept-Encoding");
|
|
MHD_add_response_header(
|
|
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
|
|
}
|
|
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
|
break;
|
|
}
|
|
|
|
case ResponseMode::REDIRECTION : {
|
|
response = MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY);
|
|
MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, m_content.c_str());
|
|
break;
|
|
}
|
|
|
|
case ResponseMode::ENTRY : {
|
|
response = MHD_create_response_from_callback(m_entry.getSize(),
|
|
16384,
|
|
callback_reader_from_entry,
|
|
new RunningResponse(m_entry, m_startRange),
|
|
callback_free_response);
|
|
MHD_add_response_header(response,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
|
MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
|
|
std::ostringstream oss;
|
|
oss << "bytes " << m_startRange << "-" << m_startRange + m_lenRange - 1
|
|
<< "/" << m_entry.getSize();
|
|
|
|
MHD_add_response_header(response,
|
|
MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
|
|
|
|
MHD_add_response_header(response,
|
|
MHD_HTTP_HEADER_CONTENT_LENGTH, kiwix::to_string(m_lenRange).c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
MHD_add_response_header(response, "Access-Control-Allow-Origin", "*");
|
|
MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
|
|
m_useCache ? "max-age=2723040, public" : "no-cache, no-store, must-revalidate");
|
|
|
|
if (m_returnCode == MHD_HTTP_OK && request.has_range())
|
|
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
|
|
|
|
if (m_verbose)
|
|
print_response_info(m_returnCode, response);
|
|
|
|
auto ret = MHD_queue_response(connection, m_returnCode, response);
|
|
MHD_destroy_response(response);
|
|
return ret;
|
|
}
|
|
|
|
void Response::set_template(const std::string& template_str, kainjow::mustache::data data) {
|
|
set_content(render_template(template_str, data));
|
|
}
|
|
|
|
void Response::set_content(const std::string& content) {
|
|
m_content = content;
|
|
m_mode = ResponseMode::RAW_CONTENT;
|
|
}
|
|
|
|
void Response::set_redirection(const std::string& url) {
|
|
m_content = url;
|
|
m_mode = ResponseMode::REDIRECTION;
|
|
m_returnCode = MHD_HTTP_FOUND;
|
|
}
|
|
|
|
void Response::set_entry(const Entry& entry) {
|
|
m_entry = entry;
|
|
m_mode = ResponseMode::ENTRY;
|
|
}
|
|
|
|
void Response::set_taskbar(const std::string& bookName, const std::string& bookTitle)
|
|
{
|
|
m_addTaskbar = true;
|
|
m_bookName = bookName;
|
|
m_bookTitle = bookTitle;
|
|
}
|
|
|
|
|
|
}
|