Can I send an NSURLRequest over a socket? - objective-c

I would like to take an NSURLRequest and turn it into some usable data that is ready to be written to a socket() instead of something like an NSURLConnection.
What is the best way to accomplish this?

NO!
You can create a NSMutableURLRequest and provide that request with a HTTPBodyStream then create a NSURLConnection to send the request.
The HTTPBodyStream is an NSInputStream the request will read the body from. You can get it value from user, file or NSData.

Related

Best way to parse SSDP Discovery in Objective C

I am working on a very simple application to discover a device using SSDP and I am trying to find the easiest way to parse the response from this command. I am trying to avoid having to do a bunch of NSString or regular expressions manipulations.
I have tried the following two approaches:
Approach 1:
Using GCDAsyncUdpSocket, I am able to successfully send the discovery command and get the following response:
HTTP/1.1 200 OK
Cache-Control: max-age=300
ST: roku:ecp
USN: uuid:roku:ecp:1234567890
Ext:
Server: Roku UPnP/1.0 MiniUPnPd/1.4
Location: http://192.168.XX.XX:8060/
This looks like a regular HTTP response, but using GCDAsyncUdpSocket, I am getting the response as an NSData object, which I can easily convert to an NSString. However, what would be ideal is to somehow cast this to an NSHTTPURLResponse and then use its methods to get the field values. Any idea if this can be done?
Approach 2:
I have tried using a regular NSURLRequest to try to send this command and then I would be able to get an NSHTTPURLResponse back. However, I keep on getting an error because the SSDP discovery command requires me to send this request to port 1900.
I use the following code to send the "HTTP" request, I know it is not strictly HTTP, but I thought it may be an easier way to send this UDP command as the requirements look very similar to HTTP.
NSURL *url = [NSURL URLWithString:#"http://239.255.255.250:1900"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[request setHTTPMethod:#"M-SEARCH *"];
[request setValue:#"239.255.255.250:1900" forHTTPHeaderField:#"Host"];
[request setValue:#"\"ssdp:discover\"" forHTTPHeaderField:#"Man"];
[request setValue:#"roku:ecp" forHTTPHeaderField:#"ST"];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
if (connection)
{
NSLog(#"Connection success!");
}
else
{
NSLog(#"Connection failed!");
}
When I do this, the connection is successful, but I get the following error in the didFailWithError delegate for NSURLConnection:
failed Error Domain=NSPOSIXErrorDomain Code=47 "The operation couldn’t be completed. Address family not supported by protocol family" UserInfo=0x8875940 {NSErrorFailingURLKey=http://239.255.255.250:1900/, NSErrorFailingURLStringKey=http://239.255.255.250:1900/}
This error only happens if I use port 1900, if I leave this out or use another more HTTP friendly port such as 8080, then this works, but obviously the device I am trying to discover does not respond correctly unless it gets the request in port 1900.
Thanks for any help you can provide.
Port 1900 is fixed for SSDP. All UPnP devices are fixed to listen on this port and the connecting nodes expect that. You can't change it. It's a part of the UPnP design goal to "work out of the box". Furthermore, my Cocoa expertise is very limited, but i think that NSHTTPURLResponse won't make things simpler for you. [NSHTTPURLResponse allHeaderFields] returns NSDictionary, which means that you don't know anymore what was the original order of the header fields. And you need to know what was coming after Ext: which is a meta-header.
I suggest either parsing the response yourself, which shouldn't be much more complicated than one cycle, separating the response by lines and then splitting by :. Or instead of trying to roll your own SSDP handshake, use ready made Cocoish library like Upnpx, Cyberlink or Platinum. It might feel like inappropriately heavy artillery just for the discovery phase, but i wonder what else you would do with the device after that, other than actually trying to invoke some actions on the device.

How to get a complete request from a NSURLRequest?

Is there any way to get the actual data that will be sent when a NSURLConnection sends a NSURLRequest? Right now I'm mainly interested in looking at HTTP and HTTPS requests, but since NSURLRequest works for many protocols, it seems like there ought to be a general way to see the corresponding data for any type of request.
Am I missing something, or do I need to construct the request myself by combining the headers, body, etc?
Just to be clear, I'd like to do this programmatically, not by watching what shows up at the server end.
Update: At this point I've written code that effectively constructs a request using the data in a NSURLRequest, so I'm not asking how to go about that. However, I'd still like to know if there's a way to access the request stream that the URL loading system would generate. In other words, can I get access to the exact bytes that would be sent if a NSURLConnection were to send my NSURLRequest?
According your last question:
Check cookiesCenter.
Check credentialsStorage.
Log all headers for example on the first urlConnection's delegate method didReceiveResponse:.
Make this connection with local webservice and try to catch all headers, params, etc.
Also, the most simple way, is making request yourself.
Am I missing something, or do I need to construct the request myself
by combining the headers, body, etc?
It turns out that this is exactly what I needed to do, and it makes sense. It's the protocol handler (NSURLProtocol subclass), not NSURLRequest, that knows how to format the request for its particular protocol. Luckily, it's very easy to render an instance of NSURLRequest into a valid HTTP request. You can get the method (GET, POST, etc.), requested resource, and host from the request, and you can also get all the HTTP headers using the -allHTTPHeaderFields method, which returns a dictionary. You can then loop over the keys in the dictionary and add lines to the request like:
for (NSString *h in headers) {
[renderedRequest appendFormat:#"%#: %#\r\n", h, [headers objectForKey:h]];
}

Webserver NSURL NSURLConnection

Ok, I'm trying to get a file from my webserver. But I'm getting kinda confused about some stuff. When I use NSURL I can get my xml-file with an url like this: "localhost...../bla.xml". But I'm also trying to test some things... Like... What will happen to my app if I have an open connection to the webserver, and I lose connection to internet? The above method with the NSURL, I haven't really established any connection where it always is connected right? or should I use be using NSURLConnection?
Maybe it's a little confusing, because I'm confused. I hope someone can give me some info I can research about.
Thanks.
Take a look at NSURLConnection Class.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html
Create connection object and set a timeout value, if you lose the connection or the connection times out NSURLConnection delegate method: - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error gets called and you would be notified of that event.
You might also use NSURLConnection sendSynchronousRequest method, but its strongly discouraged to use that method as it would block the thread its running.
Are you trying to access the content of a file? If so, you would use the following.
NSError *error;
NSURL *url = [NSURL URLwithString:#"localhost/file.html"];
NSString *filecontents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
The NSString object filecontents would contain your string.
You wouldn't be able to loose connection in such a short time. If there is no connection, an error would be applied to error.
EDIT:
If you wanted to constantly stay connected to a server, that is a different story.
You have have to use C's send() and recv() functions, which you can read about here.
I don't know much about it, and I'm learning it myself, so you should ask someone else on how to set up a server. But you will need to have another program running simultaniously.

Cocoa HTTP PUT with content-range

Is it possible to use an NSURLConnection/NSURLRequest combination to send a PUT request to a server with a Content-Range header? By that I mean I want to resume an upload to the server which can accept a byte range in the request to resume the upload.
I see you can set an NSInputStream as the request body so I figured that I could subclass that and override the the open/seek functions and set the request header but it seems to call on undocumented selectors and break the implementation.
I'm sure I could do this with CFNetwork but it just seems like there must be a way to do it with the higher level APIs.
Any ideas on where to start?
EDIT:
To answer my own question, this is indeed possible after reading a blog [http://bjhomer.blogspot.com/2011/04/subclassing-nsinputstream.html] which details the undocumented callbacks which relate to CFStream. Once those are implemented I can call the following in the open callback to skip ahead:
CFReadStreamSetProperty((CFReadStreamRef)parentStream, kCFStreamPropertyFileCurrentOffset, (CFNumberRef)[NSNumber numberWithUnsignedLongLong:streamOffset]);
Thanks,
J
I think the server needs to supports put method combines with range but this will be the way to do it with high level Objective-C API
NSURL *URL = [NSURL URLWithString:strURL];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:URL];
NSString *range = [NSString stringWithFormat:#"bytes=%lld-%lld",bytesUploaded,uploadSize];
[urlRequest addValue:range forHTTPHeaderField:#"Range"];
[urlRequest setHTTPMethod:#"PUT"];
self.connection = [NSURLConnection connectionWithRequest:urlRequest delegate:self];
Cheers
First, if you want to do fancy work with HTTP, I typically recommend ASIHTTPRequest. It's solid stuff that simplifies a lot of more complicated HTTP problems. It's not really needed for setting a simple header, but if you're starting to build something more complex, it can be nice to move over to ASI sooner rather than later.
With an NSMutableURLRequest, you can set any header you want using addValue:forHTTPHeaderField:. You can use that to set your Content-Range.
Like I posted in my comment, you can facilitate what you want without dropping down to the CoreFoundation level:
As NSInputStream inherits NSStream, it is possible to prepare the stream as follows:
NSNumber *streamOffset = [NSNumber numberWithUnsignedInteger:lastOffset];
[inputStream setProperty:streamOffset forKey: NSStreamFileCurrentOffsetKey];
(Assuming lastOffset is an NSUInteger representation of your last file offset in bytes and inputStream is the stream you want to set as the request's HTTPBodyStream.)

Retrieve file size from web server

Looking for a way to retrieve a file size from a web server using cocoa/foundation. I know one can use NSURLconnection which will return NSURLResponse that contains the file size. Is there any other way to get the size. I'm looking for a synchronous way of doing it so when i do [myClass getsize] the size is returned.
Thanks
You can use a NSMutableURLRequest to send a HTTP HEAD request (there’s a method called setHTTPMethod). You’ll get the same response headers as with GET, but you won’t have to download the whole resource body. And if you want to get the data synchronously, use the sendSynchronousRequest… method of NSURLConnection.