Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Add per HTTP request callback #3597

Closed
jonbe242 opened this issue Jan 18, 2016 · 15 comments
Closed

Add per HTTP request callback #3597

jonbe242 opened this issue Jan 18, 2016 · 15 comments
Labels
Android Mapbox Maps SDK for Android feature

Comments

@jonbe242
Copy link
Contributor

For both Android and iOS it would be nice to be able to pre process the HTTP request (okhttp3.Request and NSMutableURLRequest) before the request is sent to the webserver to be able to for example add custom headers required for your own map sources.

While at it, I think it would be a good idea to be able to construct the OkHttpClient (Android) and the NSURLSession (iOS) from custom code for similar reasons.

@tobrun tobrun added iOS Mapbox Maps SDK for iOS Android Mapbox Maps SDK for Android feature request labels Jan 18, 2016
@jonbe242
Copy link
Contributor Author

jonbe242 commented Feb 5, 2016

Looking at it I might give this a try on the Android side (at least for now). But I'd like feedback on the preferred way to do this.

Either

  • I do as suggested above and just expose a setter for the OkHttpClient in HTTPContext (easy) or
  • hide the fact that OkHttpClient is used in the public API and abstracts how HTTPRequest.start and .cancel is implemented and moves current implementation into a default implementation that a user of the SDK can choose to replace.

Any other suggestions?

jonbe242 pushed a commit to peroper/pp-mapbox-gl-native that referenced this issue Feb 8, 2016
@tobrun
Copy link
Member

tobrun commented Feb 9, 2016

@1ec5 @boundsj @friedbunny
What are your thoughts on this for iOS?
Related Android discussion can be found in #3849

@friedbunny
Copy link
Contributor

This would best be handled with a delegate method on iOS and not by directly exposing NSURLSession, as @boundsj suggested in chat.

I'm not convinced of the need for this, however. We haven't provided such low-level networking hooks in the past and the current level of demand wouldn't appear to warrant expending much/any effort on supporting this feature.

@jonbe242
Copy link
Contributor Author

@jfirebaugh I'm wondering in what direction your internal discussion went regarding an interceptor API.
Are we talking about an interceptor that can only manipulate e.g. headers or an interceptor that can replace the entire HTTP client implementation (my second suggestion/bullet above)?

@jonbe242
Copy link
Contributor Author

Depending on the wanted direction we might be interested in helping out on this.

@alexmcroberts
Copy link

Has any further consideration been given to this?

@1ec5
Copy link
Contributor

1ec5 commented Jun 9, 2016

for example add custom headers required for your own map sources

I can see this being useful if you’re maintaining your own tile server or hosting your own styles. Unfortunately, the Mapbox team is unable to go out of our way to focus on these relatively minor use cases.

A general solution would be quite complex, because the platform-specific SDK code doesn’t create these requests. Rather, the cross-platform C++ code sends these requests using an abstract interface that’s implemented further down in platform-specific adapters (such as http_file_source.mm) that don’t talk to the SDK code at all.

Fortunately, for iOS, you may be able to use Foundation’s NSURLProtocol to intercept the requests and add the HTTP headers you need. I haven’t tried this approach, Mapbox doesn’t officially support it, and I don’t know if it’s OK with the App Store, but hopefully it’ll suit your needs.

@alexmcroberts
Copy link

Unfortunately my use case is not driven by replacing headers, rather I need to use an entirely different HTTP library to load the tiles: http://www.reconinstruments.com/developers/develop/sample-apps-overview/web-connectivity-api/

Is there perhaps a way to fetch the generated URL of each tile to be requested, which I could then pass to this other library?

@1ec5
Copy link
Contributor

1ec5 commented Jun 9, 2016

To clarify, NSURLProtocol on iOS can be used for much more than adding headers. I can’t speak for the Android side, as I’m only familiar with iOS. But the fact that mbgl abstracts away platform networking differences may make it difficult to support what you’re asking for. I think the way to think about this problem would be intercepting requests, rather than delegating requests to client code.

@1ec5 1ec5 removed the iOS Mapbox Maps SDK for iOS label Jun 9, 2016
@tobrun
Copy link
Member

tobrun commented Jan 17, 2018

With #10948 we are allowing to set the used OkHttpClient, this allows to add interceptors. Closing.

@tobrun tobrun closed this as completed Jan 17, 2018
@tobrun tobrun added this to the android-v5.3.2 milestone Jan 17, 2018
@jonbe242
Copy link
Contributor Author

Nice progress!

Is there something similar in the works for iOS?

@alexmcroberts
Copy link

awesome, thanks @tobrun!

@1ec5
Copy link
Contributor

1ec5 commented Jan 18, 2018

Is there something similar in the works for iOS?

Implement -[MGLOfflineStorageDelegate offlineStorage:URLForResourceOfKind:withURL:] to transform an individual URL before the SDK issues the HTTP request. Alternatively, if you need to customize something about the request besides the URL, consider using NSURLProtocol (URLProtocol in Swift).

@Dozer1170
Copy link

Dozer1170 commented May 2, 2018

Using a custom URLProtocol doesnt work with tile requests because they use a NSURLSession. A url protocol configured with URLProtocol.registerClass(CustomProtocol.self) is not hit because NSURLSession do not respect protocols registered from URLProtocol.registerClass(). Unfortunately there isn't a way I have found to get ahold of the tile requests to add a custom header. The url protocol works for the mapbox version http call, but not for anything else. @1ec5 is there something I am missing here that you were able to get to work?

https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411560-defaultsessionconfiguration
^ Apple docs indicate that changing the protocolClasses of one configuration will not affect any other calls for the default session.

iOS Mapbox code that uses session:

class HTTPFileSource::Impl {
public:
    Impl() {
        @autoreleasepool {
            NSURLSessionConfiguration* sessionConfig =
                [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForResource = 30;
            sessionConfig.HTTPMaximumConnectionsPerHost = 8;
            sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
            sessionConfig.URLCache = nil;

            session = [NSURLSession sessionWithConfiguration:sessionConfig];

            userAgent = getUserAgent();

            accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"];
        }
    }

Here is my protocol:

import Foundation

class AuthUrlProtocol: URLProtocol {
    
    override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
        print("Init")
        super.init(request: request, cachedResponse: cachedResponse, client: client)
    }
    
    override class func canInit(with request: URLRequest) -> Bool {
        return (request.url?.absoluteString.contains("mydomain/"))!
    }
    
    override var request: URLRequest {
        get {
            var headers: [String:String] = [:]
            if super.request.allHTTPHeaderFields != nil {
                headers += super.request.allHTTPHeaderFields!
            }
            headers["Authorization"] = "AuthHeaderValue"
            let mutableRequest = (super.request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
            mutableRequest.allHTTPHeaderFields = headers
            return mutableRequest as URLRequest
        }
    }
}

canInit is only hit for this request: https://www.mapbox.com/ios-sdk/latest_version

Thank you!

@Dozer1170
Copy link

I got this to work with method swizzling in swift. While this does work it would be preferable to not have to swizzle.

For anyone else who needs this:

public extension URLSession {
      public static func swizzleSessionWithConfiguration() {
        let originalSelector = #selector(URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URLRequest, @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask)
        let swizzledSelector = #selector(URLSession.swizzledDataTaskWithRequest(with:completionHandler:))
    
        let originalMethod = class_getInstanceMethod(self, originalSelector)!
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)!
    
        let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
    
        if didAddMethod {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
    
    @objc func swizzledDataTaskWithRequest(with request: NSURLRequest,
                                           completionHandler:((Data?, URLResponse?, Error?) -> Void)?) -> URLSessionDataTask {
        var headers: [String:String] = [:]
        if request.allHTTPHeaderFields != nil {
            headers += request.allHTTPHeaderFields!
        }
        headers["Authorization"] = "AuthValue"
        let mutableRequest = (request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
        mutableRequest.allHTTPHeaderFields = headers
        return swizzledDataTaskWithRequest(with: mutableRequest, completionHandler: completionHandler)
    }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Android Mapbox Maps SDK for Android feature
Projects
None yet
Development

No branches or pull requests

7 participants