I try to get data from server. I use NSURLConnectionDelegate, NSURLConnectionDataDelegate. There is code (Objective - C).
-(void)sendRequest
{
NSURL* url = [[NSURL alloc] initWithString:#"http://SomeServer"];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
NSString* reqBody = [NSString stringWithFormat:#"<tag>Content</tag>"];
NSData* reqData = [reqBody dataUsingEncoding:NSUTF8StringEncoding];
NSInputStream* stream = [NSInputStream inputStreamWithData:reqData];
[request setURL:url];
[request setHTTPBodyStream:stream];
[request setHTTPMethod:#"POST"];
self.wpData = [[NSMutableData alloc] init];
NSURLConnection* conection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conection start];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.wpData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)d {
NSString* str = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
NSLog(#"RESPONSE DATA: %#",str);
[self.wpData appendData:d];
}
But I get "411 - Length Required" when I use
[request setHTTPBodyStream:stream];
and "HASH (someAddress)" when I use
[request setHTTPBody:reqData];
I tried
[request setHTTPBodyStream:stream];
NSString *postLength = [NSString stringWithFormat:#"%d", [reqData length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
but again "HASH (someAdddress)"
What have I done wrong?
Sorry for my English. Thanks :)
What have I done wrong?
Nothing.
HTTP Status Code 411 (Length Required) is sent by the server as a response when it refuses to accept a message without a content-length header, for whatever reason.
A server simply may or may not accept a content without a Content-Length header.
When you set an NSInputStream object as request body via property HTTPBodyStream for the request, NSURLConnection cannot evaluate the length of the body itself anymore. (there is no property length for a stream). Hence, NSURLConnection uses a certain "transfer mode", namely "chunked transfer encoding". This transfer mode should succeed to transmit any body and it does not require a Content-Legth header (actually must not contain one). Alas, the server simply does not accept this type of transfer.
See also: Chunked transfer encoding (wiki).
To solve the issue on the client side:
Determine the length of the body yourself (if possible) and set a "Content-Length" header field for the request. If this input stream has been created from a file or from a NSData object, the length can be easily determined. But be sure to set the exact same length as the actual stream content in bytes.
Don't use a NSInputStream, but use a NSData object as body and set it via property HTTPBody. When you set the body as a NSData object, NSURLConnection can determine the content length itself, and it will automatically add a Content-Length header with the correct length, unless you set it yourself in the request.
Related
I had a problem, I use the iGDB REST API which need to send some plain text for some endpoints with GET method.
There is no problem with PostMan (by selecting "Body" > "raw" & paste my query), but when I try with Objective-C, an error appear telling me "GET method must not have a body"...
Here is the code used in my app:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL"]];
[request addValue:#"text/plain" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"GET"];
[request setHTTPBody:[#"sort popularity desc;" dataUsingEncoding:NSUTF8StringEncoding]];
EDIT 02/10/2019
Trying to add each filters in headers not working...
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL"]];
[request setValue:[[#"id,name,first_release_date,release_dates,cover,platforms" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0] forHTTPHeaderField:#"fields"];
[request setValue:[[#"popularity desc" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0] forHTTPHeaderField:#"sort"];
[request setValue:[[#"5" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0] forHTTPHeaderField:#"limit"];
[request setValue:[[#"0" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0] forHTTPHeaderField:#"offset"];
Thanks in advance for any replies !
It's no longer possible to send a GET request with a body, you'll have to send the data via the query string of the URL, either by building the string manually or with the help of NS(Mutable)URLComponents.
As per the iOS 13 release notes, GET requests are no longer allowed to have a body:
All URLSessionTask instances with a GET HTTP method that contain a body now produce the error NSURLErrorDataLengthExceedsMaximum. (46025234)
This makes URLSession more conformant with the HTTP/1.1 RFC:
A message-body MUST NOT be included in
a request if the specification of the request method (section 5.1.1)
does not allow sending an entity-body in requests.
If I get true your question, you can set parameters to your request header when you get you can set like below.
[request addValue:#"valueForKey1" forHTTPHeaderField:#"key1"];
[request addValue:#"valueForKey2" forHTTPHeaderField:#"key2"];
If you send a data format so,
NSString *stringValueOfParameters =[NSString stringWithFormat:#"userName:blabla"];
NSData *convertedDat=[stringValueOfParameters dataUsingEncoding:NSUTF8StringEncoding];
NSString *headerValue=[NSString stringWithFormat:#"Basic %#",[convertedDat base64EncodedStringWithOptions:0]];
[request setValue:headerValue forHTTPHeaderField:#"headerKey"];
// Edit
You must try kinda like that, directly write a Dictionary or like below write your JSON parameters as NSString then convert it to Data.
NSDictionary *dictParams = #{ #"fields" : #[#"id",#"name",#"first_release_date",#"release_dates",#"cover",#"platforms"], #"sort" : #"popularity desc", #"limit": #5, #"offset":#0 };
// this is taken from an example
NSString *jsonString = #"{\"ID\":{\"Content\":268,\"type\":\"text\"},\"ContractTemplateID\":{\"Content\":65,\"type\":\"text\"}}";
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
Then try it to set header.
I need some help with a LoginViewController.
Basically I have a small app, and I need to post some data to the app and Im new to POST and JSON. If I can get some help and understanding that would be highly appreciated. Below are some requirements im working with. My .m file is labled as LoginViewController. This is what I have so far
-(void)setRequest {
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// A response has been received, this is where we initialize the instance var you created
// so that we can append data to it in the didReceiveData method
// Furthermore, this method is called each time there is a redirect so reinitializing it
// also serves to clear it
_responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
[_responseData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// The request has failed for some reason!
// Check the error var
}
-(void)PostRequest{
// Create the request.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://dev.apppartner.com/AppPartnerProgrammerTest/scripts/login.php"]];
// Specify that it will be a POST request
request.HTTPMethod = #"POST";
// This is how we set header fields
[request setValue:#"application/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
// Convert your data and set your request's HTTPBody property
NSString *stringData = #"some data";
NSData *requestBodyData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestBodyData;
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
}
I dont know if I'm even setting this up right. I saw many hTTP posts and what not, but im still confused on how I write this syntax and do I need to add anything additional.
I need to:
Send an asynchronous POST request to "some url"
The POST request must contain the parameters 'username' and 'password'
Will receive a JSON response back with a 'code' and a 'message'
Display the parsed code and message in a UIAlert along with how long the api call took in miliseconds
The only valid login is username: Super password: qwerty
When a login is successful, tapping 'OK' on the UIAlert should bring us back to the MainMenuViewController
I'm assuming the methods inside methods are a typo.
Unless you have a particular reason to implement all those delegate methods, you're probably better off using either
NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
// Code to run when the response completes...
}];
[task resume];
or the equivalent using NSURLConnection's sendAsynchronousRequest:queue:completionHandler: method if you still need to support iOS 6 and earlier and/or OS X v10.8 and earlier.
But the big thing you're missing is the encoding of the request body. To do that, you'll probably want to use URL encoding and specify the appropriate MIME type for that as shown here:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/WorkingwithURLEncoding/WorkingwithURLEncoding.html
Basically, you construct a string by string concatenation in the form "user=ENCODEDUSERNAME&pass=ENCODEDPASSWORD" where the two encoded values are constructed like this:
NSString *encodedString = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge NSString *)originalString,
NULL,
CFSTR(":/?#[]#!$&'()*+,;="),
kCFStringEncodingUTF8);
Do not be tempted to use stringByAddingPercentEscapesUsingEncoding: and friends. They will do the wrong thing if your strings contain certain reserved URL characters.
I would suggest that you try working with AFNetworking Library.
You can find the code here.
And a very good tutorial here.
You can do like that for this.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request addValue:#"YourUsername" forHTTPHeaderField:#"Username"];
[request addValue:#"YourPassword" forHTTPHeaderField:#"Password"];
[NSURLConnection
sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// TODO: Handle/Manage your response ,Data & errors
}];
-(IBAction)registerclick:(id)sender
{
if (_password.text==_repassword.text)
{
[_errorlbl setHidden:YES];
NSString *requstUrl=[NSString stringWithFormat:#"http://irtech.com/fresery/index.php?route=api/fresery/registerCustomer"];
NSString *postString=[NSString stringWithFormat:#"name=asd&email=sooraj&phonenumber=8111&password=soorajsnr&type=1&facebookid=&image_path="];
// _name.text,_email.text,_mobile.text,_password.text
NSData *returnData=[[NSData alloc]init];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:requstUrl]];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%lu", (unsigned long)[postString length]] forHTTPHeaderField:#"Content-length"];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
resp=[NSJSONSerialization JSONObjectWithData:returnData options:NSJSONReadingMutableContainers error:nil];
c=[[resp valueForKey:#"status" ]objectAtIndex:0];
b=[[resp valueForKey:#"message"]objectAtIndex:0];
I am following this tutorial: http://www.raywenderlich.com/2965/how-to-write-an-ios-app-that-uses-a-web-service. Trying to set up a basic web service. Seems like the tutorial is old material and ASIHTTPRequest is no longer continued. I have been trying to use NSURLRequest instead. First question, is NSURLRequest a pretty standard way to be doing this? I just want something for basic GET, POST etc, should I be doing it a different way?
My code is:
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"We want to unlock for the code %#",self.textField.text);
//Get a device ID, (actually can't do this aymore)
NSString *uniqueIdentifier = #"My iPhone";
NSString *code = self.textField.text;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.madasd.co/promos/"]];
request.HTTPMethod=#"POST";
//Set the header fields
[request setValue:#"application/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString *myString = [NSString stringWithFormat:#"rw_app_id=1&code=%#&device_id=%#",code,uniqueIdentifier];
NSLog(#"%#",myString);
NSData *requestBodyData = [myString dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody=requestBodyData;
//Create url and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[conn start];
return TRUE;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",string);
}
Second question, I have tested the backend using curl so I know it works fine, however the response I get is "Invalid Request", I think this is because the string I am sending is not correct. Am I doing this correct using the var names and & operators? Any pointers on this would be great! thanks. (Running a LAMP server on Linode!)
EDIT:
Also tried sending as JSON:
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSDictionary *mapData = [[NSDictionary alloc]initWithObjectsAndKeys:#"1",#"rw_app_id",code,#"code",uniqueIdentifier,#"device_id", nil];
NSError *error = nil;
NSData *requestBodyData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
request.HTTPBody=requestBodyData;
Still getting the same error.
A couple of thoughts:
Don't use NSURLConnection. It is deprecated as of iOS 9. Use NSURLSession. See Using NSURLSession in the URL Loading System Programming Guide.
Decide what type of request you need to prepare. You specified application/xml in your header, but are creating a application/x-www-form-urlencoded request. Your Content-Type header must match how you're building the HTTPBody.
What type of request does your server require? x-www-form-urlencoded? XML? JSON?
Also, what type of response does your server provide?
If building a application/x-www-form-urlencoded request (as suggested by the body of your request), you are not properly percent escaping the values (see https://stackoverflow.com/a/20398755/1271826).
If you use delegate based NSURLConnection or NSURLSession, you should not just grab the results in didReceiveData. What you need to do is
Instantiate a NSMutableData before starting the request;
Have didReceiveData merely append to that NSMutableData;
Only when connectionDidFinishLoading: (in NSURLConnection) or URLSession:task:didCompleteWithError: (in NSURLSession) is called, should you then use the NSMutableData.
Alternatively, if using the block-based NSURLSession, this concern is completely eliminated (since you're not implementing any delegate methods). Using completionHandler-based methods of NSURLSession is much easier.
If all of this is too complicated, you might consider using AFNetworking's AFHTTPSessionManager (but not AFHTTPRequestOperationManager) to build your requests. It gets you out of the weeds of properly building requests, implementing delegate methods, etc.
You might need to wrap the strings into a dictionary and get the NSData object from a call to NSJSONSerialization. Though it depends on the form expected by the server.
UPDATE: Apparently on iOS 5 the problem is the "Chunked-Encoding", When sending without that everything works. Seems on server that for some reason on iOS 5 the transfer never ends (on iOS 6 everything works). Anyone has a way around that?
I'm using NSURLConnection which works perfectly on iOS 6 and on simulator on same version, But when testing that on earlier devices I get response with only
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
and never with
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
Which suppose to contain my relevant data.
Here is a snippet of my code with all functions I've used (I saw that for some people removing some delegate function solved similar issue but in my case I don't have them):
-(void)establishConnection{
NSURL *url;
url = .... // Here I've set my url - it's https
self.responseData = [[NSMutableData alloc] initWithLength:0] ;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:SERVER_RESPONSE_TIMEOUT];
[request setHTTPMethod:#"POST"];
// More settings here //
....
//Accept-Language: ENUS
[request addValue:#"ENUS" forHTTPHeaderField:#"Accept-Language"];
// "Accept-Topic: Dictation"
[request addValue:#"Dictation" forHTTPHeaderField:#"Accept-Topic"];
// "Accept: text/plain"
[request addValue:#"text/plain" forHTTPHeaderField:#"Accept"];
//"Transfer-Encoding: chunked"
[request addValue:#"chunked" forHTTPHeaderField:#"Transfer-Encoding"];
NSMutableData *postBody = [NSMutableData data];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [NSString stringWithFormat:#"%#",[paths objectAtIndex:0]]; // Get sound directory
NSData *soundData = [NSData dataWithContentsOfFile: [NSString stringWithFormat:#"%#/%#",documentsDirectory, #"rec.wav"]];
[postBody appendData:soundData];
[postBody appendData:[#"\r\n" dataUsingEncoding: NSUTF8StringEncoding]];
// final boundary
//[postBody appendData:[[NSString stringWithFormat:#"--%#\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
// add body to post
[request setHTTPBody:postBody];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// You may have received an HTTP 200 here, or not...
NSLog(#"didReceiveResponse");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString* aStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"This is my first chunk %#", aStr);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connectionV {
connectionV = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Something went wrong...");
}
Please help I can't find what am I doing wrong.
You should not set Transfer-Encoding chunked yourself. NSURLConnection will set this for you when it is appropriate.
Basically, a HTTP message requires either a Content-Length header set, or it uses chunked transfer encoding where no Content-Length header must be set.
When you set the body data as a stream via request's property HTTPBodyStream AND do NOT specify the Content-Length explicitly, NSURLConnection will automatically use chunked transfer encoding and basing its decision when the body data is finished on the stream's state (detecting EOF).
Otherwise, if you set the body data via property HTTPBody with a NSData object, you might set the Content-Length explicitly, or let NSURLConnection set it for you, based on the length of the NSData object. In that case, you don't get a chunked transfer encoding.
Otherwise, if you set your body data as a stream (say a NSInputStream which you created as a file stream) AND set the Content-Length header explicitly, NSURLConnection will NOT use chunked transfer encoding.
If possible, do set the Content-Length even for an NSInputStream, that is when you are able to know in advance how large the body is. There might be servers which have trouble or are simply not capable to parse data transmitted via chunked transfer encoding, e.g. Rails with a "simple server" like WEBrick when you send JSON or XML data. Otherwise, the web server will buffer all input data anyway.
I have the following issue: I am creating a very big SOAP request (the data is a video encoded as Base64 string) and because of that I cannot send it as a raw SOAP request but rather need to send it in HTTP 1.1 chunks. I cannot seem to figure out how to do it. I used the code in here:
What are alternatives to NSURLConnection for chunked transfer encoding
but it doesn't seem to be doing what I think it should - I can see that the request arrives on the server as a single request instead of many chunks (I am using WireShark on the server to see the incoming traffic.)
I know that a similar functionality on an Android works using Apache Foundations HTTP libraries for Java - with these, any HTTP request whose length is not specified in advance is transmitted as an HTTP 1.1 Chunked Request - and I can see indeed those requests arriving on the server as individual chunks... I want to emulate that.
(UPDATE: Seems to me AFNetworking might have the functionality, but I fail to find any example as to how to use it.)
Here is my code, more or less:
NSString *soapBody = ....; //some correctly formed SOAP request XML here
NSURL *url = [NSURL URLWithString:...];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
[request addValue: ... forHTTPHeaderField:#"SOAPAction"];
[request setHTTPMethod:#"POST"];
[request addValue:#"text/xml" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[soapBody dataUsingEncoding:NSUTF8StringEncoding]];
ChunkedTransferConnection* connection = [ChunkedTransferConnection alloc];
[connection establishConnectionWithRequest:request];
where ChunkedTransferConnection implementation is the following
#implementation ChunkedTransferConnection
#synthesize p_connection;
#synthesize p_responseData;
- (void)establishConnectionWithRequest:(NSMutableURLRequest *)request
{
self.p_responseData = [[NSMutableData alloc] initWithLength:0] ;
self.p_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
...
#end
Figured it out:
NSInputStream *dataStream = [NSInputStream inputStreamWithData:[soapBody dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBodyStream:dataStream];
This causes the request automatically be in HTP 1.1 chunks!