I'm working on an app which primarily works with an API that will be installed in an internal system. The API is also accessible via the public internet. The client wants to allow users to enter both an internal and external (public internet) URL that the app will then connect to depending on availability of the internal and external URLs.
The app is basically done with the exception that it currently connects to the internal URL only for all it's API calls. I'm using AFNetworking with block-based completion/failure invocations for each API call.
Based on the logic that we have designed, the app will always check for the API's availability by querying for the server's current time. This is done by calling http://internal_url/api/time. If this API fails to return an appropriate respond, we'll switch to the external URL http://external_url/api/time and call the same API on that URL. If both fails, the app will inform the user accordingly and not perform any other queries to the API.
Without revealing too much, here's some code on how I the API calls are currently setup:
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:block failure:block {
// query /api/time and return the URL (internal/external) that is currently up
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:<url returned from above query>];
[client operationWithSuccess:block failure:block];
}
So my question would be: what is the best way to get the query /api/time method above to work? Obviously, this method needs to complete and return either the internal/external URL so that the subsequent actual API query could use. AFAIK, AFNetworking calls are block-based so it will return before the above /api/time returns. I've also thought of a separate class that uses NSURLConnection synchronously which will block the main-thread while it waits for the /api/time to return.
I'd like to tell you to simply use the same URL internally and externally (via DNS) but that's not what you want.
I think you're asking how to conditionally call the other url.
You want someAPIMethodCall to be asynchronous... so you don't want to block on the call to checking for the correct api to call.
Aside from caching the results so you don't have to do this every time, you simply want to call another block based method of your own that has a completion block which passes IN a parameter of the URL to call for your real query.
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:(void (^)(void))succesBlock failure((^)(void)):failureBlock {
[self callBlockWithMyApiUrl:^(NSString *apiUrl){
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:apiUrl];
[client operationWithSuccess:successBlock failure:failureBlock];
} onFailure:^{
failureBlock
}
}
- (NSString *)callBlockWithMyApiUrl:(NSString * (^)(void))success (void (^)(void))failure
{
// Your code to test for the working URI
// If you're doing it this way, I'd suggest caching the result.
// Subscribe to networking interface changes to dump the cache.
}
Related
This is a really annoying issue. I am using a third party login in my application. When a user logins in through the third party, it redirects an api call to the server.
ex: /api/signin/github?code=test&state=test
For some strange reason this API call is getting fetched from the service worker instead on the server which handles the login logic.
ex:
Without seeing your service worker's fetch event handler, it's hard to say exactly what code is responsible for that.
In general, though, if there are URLs for which you want to tell the service worker never to respond to, you can just avoid calling event.respondWith(...) when they trigger a fetch. There are lots of ways to avoid doing that, but an early return is straightforward:
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.pathname === '/api/signin/github') {
// By returning without calling event.respondWith(),
// the request will be handled by the normal browser
// network stack.
return;
}
// Your fetch event response generation logic goes here.
event.respondWith(...);
});
I have a progressive web-app, which speaks to an API. The calls to this api get cached by a service worker, which works great.
But now, I want to add a reload-button, which ideally forces the service worker to try to bypass the cache and update it if successful, also it should not return the cached result if a connection could not be made.
I am a bit unsure how to solve this. I am using the sw-toolbox.
All requests go through the fetch callback which receives a request object. Thus, before returning a cached response you can look for an additional header parameter (you need to include it into your request to API) to skip the logic returning cached response.
Based on your description, you are using the application cache. It can be accessed from the app fronted independent of the sw-tool box.
function onReloadButtonClicked(event) {
//Check for browser cache support
if ('caches' in window) {
//Update cache if network query is successful
caches.open('your_cache_name')
.then(function(cache) {
cache.add('your_url');
}).catch(function(err) {
// Do something with the error
});
}
}
I have an app which has a UIWebView inside of it with a loaded website. This website has a chart in it which is periodicly updated with data from remote server via websockets (socket.io).
Im new to websockets technology but Im trying to somehow intercept the chart data that the website is receiving from server via it.
Till now I have managed to catch http requests sent by the website of such address format: “http://website-address/socket.io/?auth_token=...”
I have the socket.io library for iOS but don’t know how to use it to somehow spoof the website connection and acquire the data downloaded by the website. Can anyone help? Is it even possible?
Switch to WKWebView if you can. Using javascript bridge is much easier there. That said, with UIWebView, you'd need to inject a script that adds a handler for events received by the socket that you are trying to listen to. You can either create an io variable by yourself but apparently the server needs auth token. If you cannot create an auth token, you can only do this if you have access to the io variable created by the website.
Then for adding a handler, you'll need to know what the event name is, that delivers the chart data. You can snoop around the website and see if you can find that. If you cannot all bets are off. Once we register a handler and get the data, we need to pass this back to your native code. This is where WKWebView would keep it clean by letting you add message handlers that can deliver messages from js to native code. For UIWebView you'll have to create a custom url scheme and spoof a navigation request to pass the data. Lets assume your custom url scheme is 'myApp'. Then the script you'd need to inject would be:
<script>
/* if you can access/create the auth token
var socket = io('http://website-address/socket.io/?auth_token=');
*/
var socket = getioReferenceCreatedByWebsite();
socket.on('<eventName>',function(){
window.location = 'myApp://<data>';
};
</script>
In your native code:
...
webView.delegate = self;
[webView stringByEvaluatingJavaScriptFromString:#"<theAboveJSAsAString>"];
....
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if(request.URL.scheme == #"myApp"){
NSString *data = request.URL.path;
//handle the data
return NO;
}
return YES;
}
In regards to Santhosh R answer. I had the problem he mentioned where I could not get a reference to the websocket object as it was caught up in a closure.
I solved this by adding in a preload script which wraps the native Websocket object to store any instantiated websocket objects in an array and then return the newly created websocket object.
Here is the code.
in your WebView element add in a preload attribute.
<webview id="myWebview" src="http://exmple.com" preload="./interceptor.js"></webview>
and then in inteceptor.js
window.NativeWebsocket = WebSocket;
window.WebSocket = function(url, protocols){
window.interceptedWebsockets = [];
var ws = new NativeWebsocket(url, protocols);
interceptedWebsockets.push(ws);
return ws;
}
Then, inside your WebView context you can an access array of instantiated websocket objects using window.interceptedWebsockets
I try to add a custom NSURLProtocol to a NSURLSession in order to serve test data for some calls, while providing the normal internet connectivity for all other calls. I need to do this by adding the NSURLProtocol to the session configuration's protocolClasses, as the session resides in a library that is included in the project.
For some reason my setup does deliver the test data right when my custom protocol takes the request by returning true for the canInitWithRequest call, but for all other requests (when it returns false) the default protocols that are still in the protocolClasses behind my custom protocol get the request and even return true on the canInitWithRequest call, but they never send the request to the internet and deliver a request timeout after 30 seconds instead. When I remove my custom protocol from the protocolClasses, everything works fine - just my test data is not served anymore, of course.
This is the code I use for installing the protocol:
NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSMutableArray* protocols = [NSMutableArray arrayWithArray:sessionConfiguration.protocolClasses];
[protocols insertObject:[MyProtocol class] atIndex:0];
sessionConfiguration.protocolClasses = protocols;
mySession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
When I register the custom protocol with [NSURLProtocol registerClass:[MyProtocol class]], the fallthrough does work, but as written I can't use this approach in my project.
Any idea on why this is not working correctly?
Firstly there is a misprint in your code snippet, it should be protocols instead of array variable. ))
I think that in case of custom protocol, it makes sense to see protocol client and so on( propertyFoKey: inRequest:)
Is the method startLoading(or stopLoading) is called?
Ah, I have re read your question.
In case of [NSURLProtocol registerClass] , the custom class, if being the latest registered, will handle URL scheme.
In your case it seems that there is another(void) protocol that handles the request.
Suppose, the webservice provider's server exposing the webservice in the form http://example.com:8000/api
What is best framework or library to access the webservice in ios 7 project
There are a few marshaling frameworks that support generating an object-graph from XML, however I would simply go for the following:
Invoke the service endpoint. My favorite library is BBHTTP, however you could use AFNetworking, NSURLConnection with gcd or whatever you prefer for asynch network calls.
Extract the relevant contents of the XML payload onto your use-case specific payload object using RaptureXML
I recommend having use-case specific payload objects because they model exactly what is needed for a given service invocation - supporting the notion of contract-first development. This allows you to change you internal model without effecting the integration to external systems. Similarly the external API can change without effecting your model.
You can create a category method on RXMLElement to return the element mapped to a use-case-specific object. A typical mapping usually takes just a handful of lines of code to marshal from wire-format to your payload object for the service invocation.
Here's an example (the code that I took it from wanted the payload wrapped in a SOAP envelope - just ignore that bit).
- (void)request:(MyUseCaseRequstPayload*)request onComplete:(void (^)(MyResponsePayload*))onSuccess
onError:(void (^)(NSError*))onError;
{
//Even more XML! You can stick your payload inside an envelope if you want
SoapEnvelope* envelope = [SoapEnvelope envelopeWithContent:[request xmlString]];
[[BBHTTPRequest postToURL:_serviceUrl data:[envelope data] contentType:#"text/xml"] execute:^(BBHTTPResponse* response)
{
RXMLElement* element = [RXMLElement elementFromXMLData:[response content]];
MyResponsePayload* response = [[element child:#"elementToBeMapped"] asMyObjectType];
if (onSuccess)
{
onSuccess(response);
}
} error:^(NSError* error)
{
LogDebug(#"Got error: %#", error);
if (onError)
{
onError(error);
}
}];
}