Skip to content

feat: add HTTP interceptor support#1101

Open
dballance wants to merge 2 commits intomapbox:public/dballance-add-http-interceptor-supportfrom
Tango-Tango:feat/add-http-interceptor-support
Open

feat: add HTTP interceptor support#1101
dballance wants to merge 2 commits intomapbox:public/dballance-add-http-interceptor-supportfrom
Tango-Tango:feat/add-http-interceptor-support

Conversation

@dballance
Copy link

What does this pull request do?

Adds HTTP interceptor support, in Dart, to match up to the native SDK functionality.

The interceptors are added before a map is instantiated, so that all requests (including fonts, images, etc) can be modified / logged / observed when the map is created. All map instances will share the same interceptor, much like the existing SDK functionality.

I'm opening this first to get feedback on the overall approach - will add tests if needed. Seems most of the native code is only tested indirectly, so would potentially just be an integration test of some sort.

What is the motivation and context behind this change?

Allow per request header modifications, logging, and tracing spans on map requests. Would potentially close #1062 and other stories.

Pull request checklist:

  • Add a changelog entry.
  • Write tests for all new functionality. If tests were not written, please explain why.
  • Add documentation comments for any added or updated public APIs.

PRs must be submitted under the terms of our Contributor License Agreement CLA.

@dballance dballance requested a review from a team as a code owner February 10, 2026 23:58
@dballance dballance requested a review from evil159 February 10, 2026 23:58
@CLAassistant
Copy link

CLAassistant commented Feb 10, 2026

CLA assistant check
All committers have signed the CLA.

@dballance
Copy link
Author

@mapbox/mapbox-maps-flutter - @evil159 - Any thoughts here?

@pjleonard37
Copy link
Contributor

Hi @dballance -- Thanks for the PR! We'll take a look early next week. 😀

@dballance
Copy link
Author

Great - thanks - happy to rework or extend if needed!

# Conflicts:
#	CHANGELOG.md
#	ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift
#	lib/src/mapbox_map.dart
@dballance dballance force-pushed the feat/add-http-interceptor-support branch from 4015162 to 2849f76 Compare February 13, 2026 19:30
@pjleonard37
Copy link
Contributor

Hi @dballance --

A few things came up earlier this week, but this is first on my to do list tomorrow.

@pjleonard37 pjleonard37 changed the base branch from main to public/dballance-add-http-interceptor-support February 20, 2026 15:01
@pjleonard37
Copy link
Contributor

Thanks again for the contribution @dballance!

The Dart-side API design looks good: MapboxMapsOptions as the entry point, copyWith on the request object, and the request-ID correlation pattern in the example are all good ideas. The overall approach of using a static plugin-level channel is the right approach here. That's exactly the right way to ensure pre-map-creation requests are captured. The main thing to reconsider is the synchronous blocking model, which should be replaced with the async continuation pattern. At a high level, these are the concerns I see:

  1. The blocking approach may deadlock in practice

This is the biggest issue. Both platforms use the same pattern: block the calling thread with a semaphore/latch, post to the main thread to invoke Flutter, and wait for the response. The problem is that onRequest is called synchronously on the caller's thread and the native SDK makes HTTP requests during map initialization (style, fonts, sprites) from the main thread. So the main thread blocks waiting for itself to process the Flutter invocation. Requests during map init may hit the 5-second timeout before falling through silently. As the native continuation is thread-safe and can be called from any thread, instead of blocking we should be able to just post to the main thread and call continuation.run() directly inside the Flutter result callback. Calling continuation.run() from inside the Flutter result callback on the main thread is fine because the native backend request() call inside the continuation is itself async (it queues the network request and returns immediately).

  1. There are two separate interceptor APIs that conflict with each other

The PR adds setHttpRequestInterceptor / setHttpResponseInterceptor to both MapboxMap (instance methods) and MapboxMapsOptions (static methods). Could we just use the MapboxMapsOptions approach?

  1. Putting the response body through the channel could be a heavy operation

Sending the full response body to Flutter on every intercepted response is a concern. Tile bodies are regularly 200–500 KB and during initial map load you can have dozens of these completing at once. Serializing them through the platform channel just for logging purposes may cause memory spikes and slow down map initialization noticeably. The response interceptor should omit data by default, with an explicit opt-in if body inspection is genuinely needed.

  1. Thread safety on the singleton

CustomHttpServiceInterceptor is a singleton whose mutable fields (customHeaders, flutterChannel, interceptRequests, interceptResponses) are written from the Flutter main thread and read from the HTTP network thread with no synchronization. I think a lock is needed here.

  1. Tests should be added

The deadlock won't show up in a simple unit test because you need the main thread to be the caller, but an integration test that creates a MapWidget with an interceptor already set should catch it. The response body performance issue also needs a test or at least a benchmark, since it's the kind of thing that looks fine in isolation but could degrade at scale.

@dballance
Copy link
Author

Thanks @pjleonard37!

I mostly got this done, I think. Just ran all the integration tests locally and the sample app runs as well.

I'm curious about testing for response body - what kind of benchmark were you thinking? I added some tests but unsure it covers what you're thinking about.

Future<void> setSnapshotLegacyMode(bool enable) =>
_mapInterface.setSnapshotLegacyMode(enable);

/// Set custom headers for all Mapbox HTTP requests
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to stay to make this change non-breaking, but potentially calling over to the MapboxMapOptions controller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support setting custom HTTP headers before Map initialization

3 participants

Comments