Sailsjs SocketIO in iOS - ios

I'm having a hard time wrapping my mind around a socket based chat app that I'm trying to do. I'm using sailsjs for my server side framework and trying to create a chat based app in iOS using SocketIO-Obj!
I have a successful handshake with the sailsjs framework, and the onConnect method in the sailsjs config/sockets.js file runs. but then after it's open how can I route to the controllers and actions I've created and still be able to access the request socket and subscribe them to my models

Assuming you use the latest version of sails(0.10.0 and later), the protocol that sails uses on socket.io is not public, but you can read the source on the part how it's made, and how it is interpreted.
Basically it will emit an event, with the http verb as the event name and an object method, data, urls and headers. Something like this in Javascript:
var request = {
data: data,
url: url,
headers: headers
};
socket.emit(method, request, callback);
method must be something like 'head', 'get', 'post' and etc.
data should be an object sending the request body. Optional.
url must be a string with no trailing slashes or spaces, like '/buy-a-cat' or '/cat/1/pat'.
headers should be an object, map of header names and values. Header names are lower-cased. Like { accept: '*/*' }. Optional.
I don't know much of objective-C and haven't tested this code, but you can do something like this, I think:
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:#"/cats" forKey:#"url"];
SocketIOCallback cb = ^(id argsData) {
NSDictionary *response = argsData;
// do something with response
};
[socketIO sendEvent:#"get" withData:dict andAcknowledge:cb];
Which in http request notation it would be equal to: GET /cats
Note that if you are using sails 0.9.x or lower the protocol is slightly different. Also do note that since sails isn't stable yet(not 1.x.x) this could change again, since this is not documented anywhere.
I have also found a project making sails http calls with SocketIO-Obj. It looks to be using 0.9.x protocol, but should be compatible with 1.x.x.
It looks like you can do this in it:
#import "SocketIO+SailsIO.h"
_socket = [[SocketIO alloc] initWithDelegate:self];
[_socket connectToHost:#"localhost" onPort:1337];
[_socket get:#"/user" withData:nil callback:^(id response) {
NSLog(#"Records: %#", response);
}];

Related

Cross origin for POST

I have a Jetty http server with some Jersey rest services. Those services are called from a React website that runs on a Node server.
Due to the cross origin nature of this setup, I had to add some HTTP headers. Basically, all my webservices return a createOkResult() which is created as follows.
#POST
#Path("orders/quickfilter")
#Consumes(MediaType.APPLICATION_JSON)
public Response getQuickFilterProductionOrders(String data)
{
...
return createOkResult(json.toString());
}
protected Response createOkResult(Object result)
{
return buildCrossOrigin(Response.ok().entity(result));
}
protected static Response buildCrossOrigin(Response.ResponseBuilder responseBuilder)
{
return responseBuilder.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
.allow("OPTIONS")
.build();
}
For the #GET webservices that works fine. But when I create an #POST service, I just can't get it working.
Webbrowsers (chrome and firefox) return these kind of errors:
Access to XMLHttpRequest at 'http://localhost:59187/rs/production/orders/quickfilter' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So, at first sight I would be tempted to think that the headers are still missing. The thing is, when I test this service with a tool like Postman, then all headers turn out to be in place, and the service even returns the requested data.
This is a screenshot of a POST request.
From my front-end (which runs on the node server), I use the axios API, which uses promises, and my request looks like this:
const url = "http://localhost:59187/rs/production/orders/quickfilter";
const data = JSON.stringify(request);
const headers = { headers: { "Content-Type": "application/json" } };
const promise = axios.post(url, data, headers);
Right now I have a HTTP error 500, If I remove the content type header, I get an unsupported media exception. So, I have reasons to believe that the content type is ok.
Paul Samsotha pointed me in the right direction.
I ended up adding a filter to the ServletContextHandler. Unlike the linked article, I didn't really have to create that filter from scratch. There was an existing filter class that I could use: i.e. org.eclipse.jetty.servlets.CrossOriginFilter.
FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin");
filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
filterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
Some of the above parameters can probably be left out, as they are default values. But what appeared to be crucial for me, was to set the CHAIN_PREFLIGHT_PARAM to false.
One nice side-effect, is that I can simplify the code of the actual services. They do not longer need to add special headers, by contrast they can now just return Response.ok().entity(result).build();.

Post Method with NSDictionary Values using Swift

I'm completely new toSwift. I need to hit a Post Method webservice with NSDictionary parameters & get the JSON response. I tried usingAlamofire & also NSMutableUrlRequest. Nothing seems to workout for me. I either get 'JSON text did not start with array or object and option to allow fragments not set' error or 'Undefined Variable' response from the server. The same service works fine when I try using Objective-C. As I said earlier, I am completely new toSwift & need your assistance.
My base url: http://myofficeit.in/bizfeed/webservices/client.php
Parameter I wanna Pass:
Parameter =
{
UserName = xyz;
deviceModel = iPhone;
deviceToken = "949264bc cd9c6c851ee64cc74db9078770dd7d971618ec20ce91d2e6eb9f155e";
emailid = "xyz#gmail.com";
location = Asia;
userMobileNo = 1234567890;
};
functionName = register;
The code I used for hitting the service is: http://pastebin.com/aaT4uhS7
Thanks
you can use like
let param: [String:AnyObject] = [
"UserName": iPhone,
"deviceToken": "949264bc cd9c6c851ee64cc74db9078770dd7d971618ec20ce91d2e6eb9f155e",
"emailid": "xyz#gmail.com",
"location": Asia,
"userMobileNo": 1234567890
]
Alamofire.request(.POST, "http://myofficeit.in/bizfeed/webservices/client.php/register", parameters: param).responseJSON { (req, res, json, error) in
print(req)
print(res)
print(json)
print(error)
}
for sample request in Alamofire
As broad as your question is, the broad will be my answer:
The first thing to do, is to get a clear idea about the web service API, which also requires a basic knowledge of the HTTP protocol. So, what you need to understand is, what the server expects in HTTP terminology.
You eventually will find out, how the server will expect its "parameters". Note, that there is no term like "parameters" in the HTTP protocol. So, you need to map them into something the HTTP protocol provides.
Most likely, in a POST request, "parameters" are transferred as the body of the HTTP message, as a content-type which is application/x-www-form-urlencoded, multipart/form-data or application/json.
According to the needs of the server, and with your basic knowledge of HTTP and NSURLSession, NSURLComponents etc., you compose the URL and the body of the request, set Content-Type header and possibly other headers and you are ready to go.
How this eventually looks like is given in the answer of #AnbyKarthik, which used Alamofire, and a command that composes a POST request whose parameters are send in the body whose content-type is x-www-form-urlencoded.

AFNetworking 2.0 - Forced caching

Is it possible to force response caching if it contains neither Expires or Cache-Control: max-age?
I've came across this article, but unfortunately URLSession:dataTask:willCacheResponse:completionHandler: never gets called in my AFHTTPSessionManager subclass.
Any help appreciated.
You can force the caching by implementing your own NSURLProtocol that does not follow the standard HTTP caching rules. A complete tutorial is here, which persists the data using Core Data, but the basic steps are:
Subclass NSURLProtocol
Register your subclass with +registerClass:
Return YES in your +canInitWithRequest: method if this is the first time you've seen request, or NO if it isn't
You now have two choices:
Implement your own cache storage (in which case, follow the tutorial linked above)
Inject the cache control headers that you wish the URL loading system to follow
Assuming you want #2, override connection:didReceiveResponse: in your protocol subclass to create a response that has the cache control headers you want to emulate:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
// Create a dictionary with the headers you want
NSMutableDictionary *newHeaders = [response.allHeaderFields mutableCopy];
newHeaders[#"Cache-Control"] = #"no-transform,public,max-age=300,s-maxage=900";
// Create a new response
NSHTTPURLResponse *newResponse = [[NSHTTPURLResponse alloc] initWithURL:response.URL
statusCode:response.statusCode
HTTPVersion:#"HTTP/1.1"
headerFields:newHeaders];
[self.client URLProtocol:self
didReceiveResponse:newResponse
cacheStoragePolicy:NSURLCacheStorageAllowed];
}
This will cause the response to be cached as if the server had provided these headers.
For URL sessions only, you need to set the session configuration's protocolClasses. Since you're using AFNetworking, that looks like:
[AFHTTPSessionManager sharedManager].session.configuration.protocolClasses = #[[MyURLProtocol class]]
There are some caveats, so make sure you read the protocolClasses documentation.
A few notes:
If there's any way to fix this by having your server send the appropriate headers, please, please do that instead.
For the sake of brevity I hardcoded "HTTP/1.1", but technically you should pull this out of the response.
AFNetworking uses the standard URL Loading System, and is mostly unrelated to this issue.

NSURLSession with custom authentication challenge?

Our application makes use of RESTful service calls using NSURLSession. The calls themselves are routed through a reverse proxy, to aide in session management, security, etc. on the backend. The only problem we're having is related to authentication. When a user attempts to access a protected resource -- either through a browser or a REST call -- and they are not authenticated, the reverse proxy displays an HTML login page.
The problem is, we'd like to leverage NSURLSession's ability to handle authentication challenges automatically. However, NSURLSession is unable to recognize that we're getting back an authentication challenge, because no HTTP 401 or other error code is being returned to the client. The reverse proxy sends back a 200, because the HTML was delivered successfully. We're able to recognize that it is the login page by inspecting the HTML within the client, but we'd like to be able to handle the authentication using a custom NSURLAuthenticationChallenge, if at all possible, so that NSURLSession can retry requests after authentication is successful.
Is there any way for us to recognize that we're getting back a login page within our completionHandler and tell NSURLSession that we're really getting back an authentication challenge? Or does NSURLSession require that we receive back an HTTP error code (401, etc.)
A couple of possibilities come to mind.
If you're using the delegate rendition of NSURLSession, you might be able to detect the redirect to the authentication failure page via NSURLSessionTaskDelegate method willPerformHTTPRedirection.
You can probably also detect the redirect by examining the task's currentRequest.URL and see if a redirect happened there, too. As the documentation says, currentRequest "is typically the same as the initial request (originalRequest) except when the server has responded to the initial request with a redirect to a different URL."
Assuming that the RESTful service generally would not be returning HTML, you can look at the NSURLResponse, confirm that it's really a NSHTTPURLResponse subclass, and then look at allHeaderFields to confirm whether text/html appears in the Content-Type of the response. This obviously only works if the authentication page returns a Content-Type that includes text/html and the rest of your responses don't (e.g. they're application/json or something like that).
Anyway, that might look like:
NSString *contentType = [self headerValueForKey:#"Content-Type" response:response];
if ([contentType rangeOfString:#"text/html"].location != NSNotFound) {
// handle it here
}
Where, headerValueForKey might be defined as follows:
- (NSString *)headerValueForKey:(NSString *)searchKey response:(NSURLResponse *)response
{
if (![response isKindOfClass:[NSHTTPURLResponse class]])
return nil;
NSDictionary *headers = [(NSHTTPURLResponse *) response allHeaderFields];
for (NSString *key in headers) {
if ([searchKey caseInsensitiveCompare:key] == NSOrderedSame) {
return headers[key];
}
};
return nil;
}
At worst, you could detect the HTML response and parse it using something like HPPLE and programmatically detect the authentication HTML response.
See Wenderlich's article How to Parse HTML on iOS.
Frankly, though, I would prefer to see a REST response to report the authentication error with a REST response (or a proper authentication failure challenge or a non 200 response) rather than a redirect to a HTML page. If you have an opportunity to change the server response, that would be my personal preference.
Authentication challenges are triggered by the WWW-Authenticate header in a 40x response.
From the documentation :
The URL loading system classes do not call their delegates to handle request challenges unless the server response contains a WWW-Authenticate header. Other authentication types, such as proxy authentication and TLS trust validation do not require this header.
Unfortunately the proxy authentication referred to above does not apply in your reverse proxy/accelerator scenario.
Have you tried simply setting the username and password to the authorization header of NSMutableURLRequest as such.
NSString *authorizationString = [[[NSString stringWithFormat:#"%#:%#",user, password] dataUsingEncoding:NSUTF8StringEncoding] base64Encoding];
[yourRequest setValue:[NSString stringWithFormat:#"Basic %#", authorizationString] forHTTPHeaderField:#"Authorization"];
This should work.
You could try using an HTML Parser library (like https://github.com/nolanw/HTMLReader).
Then in the completion handler block, you can evaluate the response to check if it is an HTML page, and if it contains a login form.
I don't know this library, but I guess you can use it like this:
HTMLDocument *document = [HTMLDocument documentWithString:responseObjectFromServer];
HTMLNode *node = [document firstNodeMatchingSelector:#"form.login"];
if(node) {
// Perform second NSURLSession call with login data
}

RESTKit response.url changed order of parameters

I'm using RESTKit to get data from a rest-api.
This is the URL i set for my request, here's the log just before the request goes off.
2014-04-03 15:51:10.186 xxx[35745:60b] Just sent URL: /api/dspObjGetNewsList?action=coverage&count=30&start=0&open=0&user=xxx&unique=36027&type=all&country=Sweden,global&division=Strategic Industries,Regional Sales and Service,Automotive
Then i log the reponse URL.
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"xxx: %d, url: %#", [response statusCode], response.URL);
And i get this?
xxx: response code: 200, url: url/api/dspObjGetNewsList?unique=26791&type=all&division=Strategic%20Industries%2CRegional%20Sales%20and%20Service%2CAutomotive&user=xxx&action=coverage&open=0&country=Sweden%2Cglobal&count=30&start=0
Why am i getting a different URL in my response? Does RESTKit modify my url?
Have you configured HTTPClient properly? Use - (id)initWithHTTPClient:(AFHTTPClient *)client method to configure HTTPClient. For instance:
AFHTTPClient *HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"www.url.com"]];
Your first log appears to be a raw string of the URL. The second log appears to be the % escaped URL version of that string. This isn't a RestKit thing, it's a URL loading system thing. Certain characters need to be escaped so that they are valid for use in a URL.
For example, your original string has a number of spaces in it. This isn't allowed in a URL and each must be changed to %20.
Why the parameters change order isn't clear - it depends on how you created the string and supplied the parameters to RestKit. But, the order doesn't matter to the processing so you shouldn't need to worry about it.

Resources