Skip to content

Intercept network requests

Three extension points intercept resource requests. A resource transform rewrites a URL before the network request. An HTTP header transform adds headers to the request that follows. A resource provider supplies the response bytes. Register each one on the runtime owner thread.

MapLibre can invoke any of these callbacks concurrently on network and worker threads. Protect every value that a callback reads and return quickly because the resource request waits. A transform callback sets a replacement URL. A header transform callback sets header names and values. A provider callback operates only on the request handle that it received.

Rewrite a URL to add a credential or signature, or to select another host. The transform runs for every network resource, including nested PMTiles range requests. File, asset, MBTiles, and cache-database resources bypass it.

Compare the URL against your own host before you attach a credential, because a style names glyphs, sprites, and tiles on origins that you do not control.

resource-transform.c
const char* api_key = user_data;
(void)kind;
if (strncmp(url, trusted_prefix, sizeof(trusted_prefix) - 1) != 0) {
return MLN_STATUS_OK; // An empty response preserves the original URL.
}

Build the replacement URL in host storage, then set it on the response. Returning a non-OK status preserves the original request.

resource-transform.c
char rewritten[2048];
if (!build_keyed_url(url, api_key, rewritten, sizeof(rewritten))) {
return MLN_STATUS_OK;
}
// The helper copies the URL before rewritten leaves scope.
return mln_resource_transform_response_set_url(
out_response, rewritten, strlen(rewritten)
);

Registering a transform replaces the previous transform. After the registration call returns, the replaced transform has no in-flight callbacks.

resource-transform.c
void install_transform(mln_runtime runtime, char* api_key) {
mln_resource_transform transform = {
.size = sizeof(transform),
.callback = add_api_key,
.user_data = api_key,
};
mln_runtime_set_resource_transform(runtime, &transform);
}

Add a header to carry a bearer token or another credential that belongs in the request rather than in the URL. A header transform runs after URL transformation and immediately before the HTTP client sends the request. It reaches online and offline requests, including nested PMTiles range requests. Cache hits, non-HTTP schemes, and requests that a provider answered bypass it.

Compare the transformed URL against your own origin before you return a credential, because the callback runs for every network resource.

http-header-transform.c
const char* token = user_data;
(void)kind;
if (strncmp(url, trusted_prefix, sizeof(trusted_prefix) - 1) != 0) {
return MLN_STATUS_OK; // Every other origin gets no header.
}

Set each name and value on the response. Returning a non-OK status discards the headers from that invocation and sends the request unchanged.

http-header-transform.c
// The helper copies the name and the value before it returns.
return mln_http_header_transform_response_set(
out_response, header_name, sizeof(header_name) - 1, token, strlen(token)
);

Register the transform on the runtime, and use mln_runtime_clear_http_header_transform to stop adding headers. Both calls work while maps are live, so a host installs a refreshed credential without recreating the runtime.

http-header-transform.c
void install_header_transform(mln_runtime runtime, char* token) {
mln_http_header_transform transform = {
.size = sizeof(transform),
.callback = add_authorization,
.user_data = token,
};
mln_runtime_set_http_header_transform(runtime, &transform);
}

A redirect that keeps the scheme, host, and effective port carries the headers to its destination. A redirect that changes any of the three removes every header that the transform added, and it invokes no transform for the destination. Registration returns an unsupported status on OpenHarmony and in the browser, whose HTTP clients cannot make that distinction. Serve those requests with a provider instead, as the next section shows.

Use a provider for host-managed storage, such as an application archive or a custom URL scheme. MapLibre routes unrecognized schemes to the network layer, where the provider runs before the online file source. A provider response bypasses the transform.

Match the requests that the host serves, and pass every other request to MapLibre’s network stack. Fetch the resolved URL after tile-server normalization and API-key insertion. The cache uses the requested URL as the logical identity.

resource-provider.c
const asset_store* store = user_data;
const uint8_t* bytes = NULL;
size_t size = 0;
if (
strncmp(request->resolved_url, "app://", 6) != 0 ||
!asset_store_find(store, request->resolved_url, &bytes, &size)
) {
return MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH;
}

A provider with the bytes available completes the request inside the callback, releases the handle, and reports that it handled the request.

resource-provider.c
const mln_resource_response response = {
.size = sizeof(response),
.status = MLN_RESOURCE_RESPONSE_STATUS_OK,
.error_reason = MLN_RESOURCE_ERROR_REASON_NONE,
.bytes = bytes,
.byte_count = size,
};
// The C API copies these bytes before the call returns.
mln_resource_request_complete(handle, &response);
mln_resource_request_release(handle);
return MLN_RESOURCE_PROVIDER_DECISION_HANDLE;

Registering a provider replaces any previous one. Handles that the previous provider took stay valid. Complete and release each of them as usual.

resource-provider.c
void install_provider(mln_runtime runtime, asset_store* store) {
const mln_resource_provider provider = {
.size = sizeof(provider),
.callback = serve_bundled_asset,
.user_data = store,
};
mln_runtime_set_resource_provider(runtime, &provider);
}

A provider with an I/O pool reports the same decision and retains the handle. Complete the request from any thread when the bytes arrive. Check for cancellation before expensive work. Release each handle exactly once, after completion or cancellation.

The runtime stores each callback and its user data by reference. Keep callback state reachable until replacing or clearing the callback, or destroying the runtime. Each operation waits for in-flight callbacks to return. Release any host lock that a callback also acquires before destroying the runtime. Holding that lock can deadlock destruction.