Support for serving customized resources

During work on the kiwix-serve front-end, the edit-save-test cycle is
a multistep procedure:

1. build and install libkiwix
2. build kiwix-tools
3. run kiwix-serve
4. reload the web-page in the browser

When making changes in static resources that are served by kiwix-serve
unmodified, the steps 1-3 can be eliminated if kiwix-serve is capable of
serving resources from the file-system. This commit adds such a
functionality to kiwix-serve. Now, if during startup of kiwix-serve the
environment variable `KIWIX_SERVE_CUSTOMIZED_RESOURCES` is defined it is
assumed to point to a file where every line has the following format:

URL MIMETYPE RESOURCE_FILE_PATH

When a request is received by kiwix-serve and its URL matches any of the
URLs read from the customized resource file, then the resource data is
read from the respective file RESOURCE_FILE_PATH and served with
mime-type MIMETYPE.

Though this feature was introduced in order to facilitate the
development of the iframe-based content viewer, it can also be useful to
users who would like to customize the kiwix-serve front-end on their own
(without re-building all of kiwix-serve).

There is some overlap with a feature of the kiwix-compile-resources
script that also allows to override resources. The differences are:

1. The new way of customizing front-end resources has all such resources
   listed in a text file and there is a single environment variable
   from which the path of that file is read. kiwix-compile-resources
   associates a separate environment variable with each resource.

2. The new way uses regular paths to identify a resource. The
   kiwix-compile-resources method encodes the resource path by replacing
   any non-alphanumeric characters (including the path separator) with
   underscores (so that the resulting resource identifier can be used
   to construct the name of the environment variable controlling that
   resource).

3. The new method allows adding new front-end resources. The old method
   only allows to modify existing resources.

4. The new method allows (actually requires) to specify the URL at which
   the overriden resource should be served (similarly, the MIME-type can/must
   be specified, too). The old method only allows to override the contents of
   a resource.

5. The new method only allows to override front-end resources that are
   served without any preprocessing by kiwix-serve at runtime. The old
   method allows to override template resources as well (note that
   internationalization/translation resources cannot be overriden using the
   old method, either).
This commit is contained in:
Veloman Yunkan 2022-06-04 13:18:16 +04:00 committed by Kelson
parent 6938253e59
commit e3e4bfa533
2 changed files with 72 additions and 3 deletions

View File

@ -68,6 +68,7 @@ extern "C" {
#include <string>
#include <vector>
#include <chrono>
#include <fstream>
#include "kiwixlib-resources.h"
#ifndef _WIN32
@ -212,6 +213,12 @@ void checkBookNumber(const Library::BookIdSet& bookIds, size_t limit) {
}
}
struct CustomizedResourceData
{
std::string mimeType;
std::string resourceFilePath;
};
} // unnamed namespace
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
@ -327,7 +334,6 @@ zim::Query SearchInfo::getZimQuery(bool verbose) const {
return query;
}
static IdNameMapper defaultNameMapper;
static MHD_Result staticHandlerCallback(void* cls,
@ -339,6 +345,27 @@ static MHD_Result staticHandlerCallback(void* cls,
size_t* upload_data_size,
void** cont_cls);
class InternalServer::CustomizedResources : public std::map<std::string, CustomizedResourceData>
{
public:
CustomizedResources()
{
const char* fname = ::getenv("KIWIX_SERVE_CUSTOMIZED_RESOURCES");
if ( fname )
{
std::cout << "Populating customized resources" << std::endl;
std::ifstream file(fname);
std::string url, mimeType, resourceFilePath;
while ( file >> url >> mimeType >> resourceFilePath )
{
std::cout << "Got " << url << " " << mimeType << " " << resourceFilePath << std::endl;
(*this)[url] = CustomizedResourceData{mimeType, resourceFilePath};
}
std::cout << "Done populating customized resources" << std::endl;
}
}
};
InternalServer::InternalServer(Library* library,
NameMapper* nameMapper,
@ -368,9 +395,12 @@ InternalServer::InternalServer(Library* library,
mp_library(library),
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U)))
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
m_customizedResources(new CustomizedResources)
{}
InternalServer::~InternalServer() = default;
bool InternalServer::start() {
#ifdef _WIN32
int flags = MHD_USE_SELECT_INTERNALLY;
@ -507,6 +537,9 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
if ( etag )
return Response::build_304(*this, etag);
if ( isLocallyCustomizedResource(request.get_url()) )
return handle_locally_customized_resource(request);
if (startsWith(request.get_url(), "/skin/"))
return handle_skin(request);
@ -1067,4 +1100,34 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
}
}
bool InternalServer::isLocallyCustomizedResource(const std::string& url) const
{
return m_customizedResources->find(url) != m_customizedResources->end();
}
std::unique_ptr<Response> InternalServer::handle_locally_customized_resource(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_locally_customized_resource\n");
}
const CustomizedResourceData& crd = m_customizedResources->at(request.get_url());
if (m_verbose.load()) {
std::cout << "Reading " << crd.resourceFilePath << std::endl;
}
const auto resourceData = getFileContent(crd.resourceFilePath);
auto byteRange = request.get_range().resolve(resourceData.size());
if (byteRange.kind() != ByteRange::RESOLVED_FULL_CONTENT) {
return Response::build_416(*this, resourceData.size());
}
return ContentResponse::build(*this,
resourceData,
crd.mimeType,
/*isHomePage=*/false,
/*raw=*/true);
}
}

View File

@ -109,7 +109,7 @@ class InternalServer {
bool blockExternalLinks,
std::string indexTemplateString,
int ipConnectionLimit);
virtual ~InternalServer() = default;
virtual ~InternalServer();
MHD_Result handlerCallback(struct MHD_Connection* connection,
const char* url,
@ -142,6 +142,7 @@ class InternalServer {
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
std::unique_ptr<Response> handle_content(const RequestContext& request);
std::unique_ptr<Response> handle_raw(const RequestContext& request);
std::unique_ptr<Response> handle_locally_customized_resource(const RequestContext& request);
std::vector<std::string> search_catalog(const RequestContext& request,
kiwix::OPDSDumper& opdsDumper);
@ -153,6 +154,8 @@ class InternalServer {
std::pair<std::string, Library::BookIdSet> selectBooks(const RequestContext& r) const;
SearchInfo getSearchInfo(const RequestContext& r) const;
bool isLocallyCustomizedResource(const std::string& url) const;
private: // data
std::string m_addr;
int m_port;
@ -176,6 +179,9 @@ class InternalServer {
std::string m_server_id;
std::string m_library_id;
class CustomizedResources;
std::unique_ptr<CustomizedResources> m_customizedResources;
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw);
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);