NSInputStream for Twitter Streaming API iOS? - ios

Simple question:
Can I use NSInputStream to get data from the streaming API?
If so, can someone outline what this can look like.
I have tried something like
_twitterStream = [[NSInputStream alloc]initWithURL:[NSURL URLWithString:#"https://sitestream.twitter.com/1.1/site.json"]];;
_twitterStream.delegate = self;
[_twitterStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
and adding Delegate Methods, but this is not working. I realize I need to specify what users I need, however, above code is what I have so far.
Thanks!

One approach is using input streams and I do not think you would be able to pull it of easily as connecting to the streaming api requires a couple key http request headers.
CFReadStreamRef readStream;
CFStreamCreatePairWithSocketToHost(nil, (CFStringRef)#"Your IP Here", PORT_NUMBER, &readStream, nil);
self.stream = (__bridge NSInputStream *)(readStream);
[self.stream setDelegate:self];
[self.stream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
Is a typical pattern of connecting to a host server with an input stream.
Another way is to just directly hit the URL like you do.
The problem with these approaches is that they do not address the authentication issues.
The easiest way I found was to use an NSURLSession task with the correct request headers set. Then you start receiving the data packets in the sessionDataDelegate.
Generating the twitter OAuth header can be worse than a prostate exam (trust me on this).
https://dev.twitter.com/oauth/overview/authorizing-requests
Here is a demo project where I hit the stream and dequeue searched for tweets in a tableview.
https://github.com/GregPrice24/TwitterStreamingAPI

Related

SocketRocket not throwing error on connecting invalid host

I am developing an iPhone App (using iOS 9 beta). I am using Socket connections for which I am using SocketRocket client library. But when I try to establish a wss connection with some invalid host name, I don't get any error on opening socket, connection or even on sending data, so whenever I try to run program it seems like information about host is correct and data is being sent.
I am using current version of SocketRocket library, I have added SRWebSocket.h, SRWebSocket.m and SocketRocket-Prefix.pch files in my project. Following is the part of code I have:
NSString* url = [NSString stringWithFormat:#"wss://%#/myproject/stream?data=%d", webSocketHost, dummyData];
SRWebSocket *webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
webSocket.delegate = self;
// open websocket
[webSocket open];
// send message to websocket
[webSocket send:[self getJSONString:parameters]];
// close websocket
[webSocket close];
webSocket = nil;
If I pass some random host name like "abc.def" for the variable webSocketHost, it will still run smoothly (I have try-catch blocks surrounding above code, and I also tried to put break points in between and debugged it line by line).
And even when I don't have any internet connection to my phone, there aren't any errors thrown.
Does anyone know what could be the problem?
Thanks!
Are you implementing this delegate method? The library won't throw an error when you call [webSocket open], it will call this method if it can't connect to the endpoint sometime in the future since establishing a connection is an asynchronous operation.
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;

How can I implement STARTTLS on iOS?

I would like to implement STARTTLS in an iOS application but I'm having trouble figuring out from the documentation how to do it.
So far I have:
I create the socket with CFStreamCreatePairWithSocketToHost and open the streams:
CFStreamCreatePairWithSocketToHost(
NULL, (__bridge CFStringRef)host, port,
&read_stream, &write_stream
);
reader = objc_retainedObject(read_stream);
[ reader setDelegate:self ];
[ reader scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode ];
[ reader open ];
writer = objc_retainedObject(write_stream);
[ writer setDelegate:self ];
[ writer scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode ];
[ writer open ];
I get the right callbacks when data are available on the streams so the connection is working.
I successfully interact with the server in plain text and negociate STARTTLS.
Eventually, the server sends the go-ahead for STARTTLS:
. OK Begin TLS negotiation now.
Now it is time to upgrade the socket form plaintext to TLS. What do I do next?
I thought I should do this as per Apple's documentation:
[ reader setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey ];
[ writer setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey ];
But that doesn't seem to do anything. I'm not surprised that it doesn't work since the documentation says quite clearly:
You must set the property before you open the stream.
Yet of course the stream must be already opened in this case since it is used to conduct the plaintext STARTTLS negociation!
I cannot find any documentation on how to upgrade a socket from plaintext to SSL or perhaps how to layer a new set of SSL-encrypted streams on top of a set of input&output plaintext streams.
I could not find any way to implement STARTTLS using the high-level CFStream API. There is CFStreamCreatePairWithSocket which allows you to connect your own socket and then apply TLS to it afterwards, but there is no way to get the library to verify the remote host name against the certificate host name.
The only way to do it appears to be using this much lower level library: Secure Transport Reference.

iOS: Socket Connection Error in sending buffer

We have tried too many diffrent approaches to solve our issue and also searched over net but we only found example of sending string and not the byte array. We require to send byte array through socket connection. Please read issue explanation below.
We require to connect wi-fi device with iOS app. We have successfully connected the device but when we send the command in byte array format, It is returning NSStreamEventHasSpaceAvailable in response. Which is wrong, We require NSStreamEventHasBytesAvailable event.
Below is the code for connection:
-(void) initNetworkCommunication:(NSString*)strHostName {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)strHostName, 2000, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
When connection is open, we are calling below method to send the command:
- (IBAction) sendCommand {
unsigned char *buffer[5] = {0x3a,0x04,0x01,0x07,0x0c};
NSData *data = [NSData dataWithBytes:buffer length:5];
[outputStream write:[data bytes] maxLength:5];
}
So there is some issue in sendCommand method because we are receiving NSStreamEventHasSpaceAvailable and that is wrong. Because it should be returned NSStreamEventHasBytesAvailable in the response. Can any one please help us how to send byte array {0x3a,0x04,0x01,0x07,0x0c} in iOS so we can receive NSStreamEventHasBytesAvailable event.
According to command manual, When device receives command in correct format then it will return acknowledgement. Below is the manual description.
All commands are 16 bits and are placed in the Data byte 1(MSB) and
Data byte 2(LSB).
The response to a command can either be a specific response relating
to the command or a simple response. The three simple responses are
ACK, NAK and UNK. An ACK signifies that the command was received
successfully. A NAK indicates that there was an error with either the
length byte or an incorrect checksum was calculated. An UNK response
indicates that the recipient does not recognize the command that was
sent.
Value Answer Status
0x06 = ACK (OK)
0x15 = NAK (NOT OK)
0x09 = UNK (Unknown command)
So we should receive any of the above flag(ACK OR NAK OR UNK) but we are receiving NSStreamEventHasSpaceAvailable and this is wrong. Any one please help me to solve my problem.
Thanks in advance.
NSStreamEventHasSpaceAvailable is the correct event to receive after sending data on an output stream - it indicates that you can write at least one byte without blocking (ie. There is space to write data available). NSstreamEventHasBytesAvailable will be signalled against an input stream when data is received on the socket associated with that input stream to indicate that you can issue a read without blocking.
If the device you are sending the data to responds to that data then I would expect you to receive NSStreamEventHasBytesAvailable on inputStream

How to use NSURLCache to return cached API responses when offline (iOS App)

I'm hoping someone can shed some light on a few things I've been researching but not making much progress on.
I'd like to take advantage of NSURLCache to return cached responses for API calls that I make within an iOS App. When the device is online, I'd like to return the cached response if it's recent enough, otherwise fetch from remote. When the device is offline I'd like to immediately return the cached response (if any) regardless of it's age.
I'm using AFNetworking. The API calls that I'm making are to a server I control. Protocol is HTTPS. The Cache-Control response header is currently "max-age=0, public". I'm not currently setting cache related request headers (should I?). I set the request's cache policy to be NSURLRequestReturnCacheDataDontLoad when offline and use the default NSURLRequestUseProtocolCachePolicy when online. And I can see the requests and their responses in the default Cache.db on disk. However when I'm offline all requests fail (no cached responses (despite appearing to have been cached) are being used/returned.
Per http://nshipster.com/nsurlcache/ I initialize a sharedURLCache in didFinishLaunchingWithOptions and set the AFNetworking setCacheResponse block to something like this:
NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowed;
return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:mutableData userInfo:mutableUserInfo storagePolicy:storagePolicy];
I've read these and other posts on the topic:
http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/
http://blackpixel.com/blog/2012/05/caching-and-nsurlconnection.html
I'm wondering if anyone out there has successfully achieved this functionality before using the standard NSURLCache (also interested in success stories involving SDURLCache but Peter S. says as of iOS5 disk caching is supported therefore SDURLCache is no longer needed, ie I'd like to use the default built in cache).
Thanks in advance!
Did you see this post?
AFNetworking (AFHttpClient) offline mode not working with NSURLRequestReturnCacheDataDontLoad policy
Looks like it might be a bug with iOS 6. Here is what Robert Mao had to say in the post:
A summarized work around is read the response from cache, instead of
use NSURLRequestReturnCacheDataDontLoad policy:
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse != nil &&
[[cachedResponse data] length] > 0)
{
// Get cached data
....
}
Unless all of your calls are 100% GET and free of side effects or time dependency then this is dangerous.

Sending push notifications with NSOutputStream returns -9844

I'm trying to write OS X app sending push notifications with use of NSStreams. Everything seems to be fine but i get back error -9844 when sending data to gateway.sandbox.push.apple.com. From SecureTransport.h
errSSLConnectionRefused = -9844, /* peer dropped connection before responding */
This is how I create and open output stream:
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)#"gateway.sandbox.push.apple.com", 2195, &readStream, &writeStream);
self.outputPushStream = (__bridge_transfer NSOutputStream *)writeStream;
[self.outputPushStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
self.outputPushStream.delegate = self;
[self.outputPushStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputPushStream open];
And upon receiving NSStreamEventOpenCompleted event in NSStream delegate I send data:
const uint8_t *data = pushData.bytes;
[self.outputPushStream write:data maxLength:pushData.length];
I'm guessing that this may be some issue with certificates. I've got dev certificate and key for push notifications in keychain. I don't have any experience with SSL or NSStream so I don't really know where to look next.
I'm not sure if certificate in a keychain is enough for CFStreamCreatePairWithSocketToHost to set up an ssl connection. I found this code which does pretty much the same you want to do, but it passes certificate with code (see configureStreams and certificateArray methods).

Resources