I'm attempting to set custom headers on a per-request basis using AFNetworking, but occasionally the headers will seemingly disappear after being set. Below is the code used to make a request...
+ (void) getWithURI: (NSString*) uri header: (NSDictionary*) header success: (NSString*) successCallback failure: (NSString*)errorCallback dispatch: (NSString*)dispatchedId
{
createManagerInstance();
AFHTTPRequestOperation* operation = [manager GET:uri
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[RestWrapper succeededWithJson:operation.responseString dispatchedId:dispatchedId successCallback:successCallback];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[RestWrapper failedWithJson:operation.responseString dispatchedId:dispatchedId errorCallback:errorCallback];
}];
NSMutableURLRequest* request = (NSMutableURLRequest*)operation.request;
for (NSString* key in header)
{
if([request valueForHTTPHeaderField:key] != nil)
{
[request setValue:header[key] forHTTPHeaderField:key];
}
else
{
[request addValue:header[key] forHTTPHeaderField:key];
}
}
NSLog(#"Headers: %#", request.allHTTPHeaderFields);
[operation start];
}
For 95% of my requests, they go through as anticipated. Sporadically, however, some will fail and indicate a header is missing. This has been confirmed by capturing the requests in question using Fiddler and seeing that the headers are actually missing. Despite this, the console log of request.allHTTPHeaderFields always shows the headers in place.
The only other thing I noticed is that in general Fiddler reports the caching policy as "max-age=0, private, must-revalidate" for each request. However, whenever a request loses the custom headers, it's caching policy is "no-cache".
This is because you're adding HTTP Header fields in wrong way.
You should add it before request. You may try something like this :
+ (void) getWithURI: (NSString*) uri header: (NSDictionary*) header success: (NSString*) successCallback failure: (NSString*)errorCallback dispatch: (NSString*)dispatchedId{
createManagerInstance();
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:header[key] forHTTPHeaderField:key];
AFHTTPRequestOperation* operation = [manager GET:uri
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[RestWrapper succeededWithJson:operation.responseString dispatchedId:dispatchedId successCallback:successCallback];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[RestWrapper failedWithJson:operation.responseString dispatchedId:dispatchedId errorCallback:errorCallback];
}];
[operation start];
}
Related
I updated afnetwork to 2.0 due to 64 bit. I made a mistake at first using AFHTTPRequestSerializer, but the server side accept Json only. The wried thing was it works fine for me even I used AFHTTPRequestSerializer, so I didn't notice this problem and released the error app to public. Then I keep receiving user complain and somehow I rebuild my app on my device, and local server can catch my wrong content-type(application/x-www.form-urlencoded). After I updated the AFHTTPRequestSerializer to AFJSONRequestSerializer, it doesn't fix the problem, but the user still keep sending wrong content-type to server even they updated the app. New user has no problem, only happen on old user.
Is this related to cache problem?
Server Log:
10:22:53 AM] Mark 1 contentType is: application/x-www-form-urlencoded, method is: POST headers are: Connection=keep-alive&Content-Length=1880739&Content-Type=application%2fx-www-form-urlencoded&Accept=*%2f*&Accept-Encoding=gzip%2c+deflate&Accept-Language=en%3bq%3d1&Cookie=ASP.NET_SessionId%3dbby5dlk5afmsnqnoqpprvqpw&Host
10:04:56 AM] contentType is: application/json, method is: POST header is: Connection=keep-alive&Content-Length=40352 4&Content-Type=application%2fjson &Accept=*%2f*&Accept-Encoding=gzip%2c+deflate&Accept-Language=en%3bq%3d1&Cookie=ASP.NET_SessionId%3dcw3gdkg3sff1f0j50z2rnism&Host
my code is
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
AFHTTPRequestOperation *operation = [manager POST:_url parameters:requestParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"operation success: %#\n %#", operation, responseObject);
NSDictionary *decoded = [self processResponce:responseObject failureBlock:failureBlock];
if (!decoded) return;
BOOL containsError = [self checkErrorStatus:decoded failureBlock:failureBlock];
if (containsError) return;
successBlock(decoded[#"Data"]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failureBlock(FailureTypeUnknown, [error localizedDescription]);
}];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
progressBlock(bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
}];
as per your response, please set your manager's content type as below
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
Using the Postman extension for Chrome I can successfully POST some JSON. Using Charles to inspect the request, I see that the request data is as follows:
{
"query": {
"term": {
"user_id": "12345"
}
}
}
When I try to construct this same request using AFNetworking 2.4.1, I can see that the data is formatted as:
query[term][user_id]=12345
The server of course returns an error.
What part of the POST request am I getting wrong?
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"query":#{#"term":#{#"user_id":#"12345"}}};
[manager POST:#"http://someURL" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"SUCCESS %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"FAIL: %#", error);
}];
The short answer is:
manager.requestSerializer = [AFJSONRequestSerializer serializer];
From the documentation:
Requests created with requestWithMethod:URLString:parameters: &
multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:
are constructed with a set of default headers using a parameter
serialization specified by this property. By default, this is set to
an instance of AFHTTPRequestSerializer, which serializes query
string parameters for GET, HEAD, and DELETE requests, or
otherwise URL-form-encodes HTTP message bodies.
I have tried several StackOverflow questions, and I caanot find the correct answer on this. I am using the POSTMAN plugin for Chrome to check my REST calls and I cannot figure out why I cannot read the response. In the comments you will see all the different attempts I have made to get the response.
NSDictionary* session_params = #{SESSION_USERNAME_KEY:SESSION_USERNAME_VALUE, SESSION_PASSWORD_KEY:SESSION_PASSWORD_VALUE};
NSURL* url = [NSURL URLWithString:SESSION_URL];
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:url];
//GET THE **** THING TO INTERPRET A TEXT response
//[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:RKMIMETypeTextXML];
//[objectManager setAcceptHeaderWithMIMEType:#"text/html"];
//[objectManager setAcceptHeaderWithMIMEType:RKMIMETypeTextXML];
//[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"text/html"];
//[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"text/html"];
//[objectManager setRequestSerializationMIMEType:#"text/html"];
//END
NSMutableURLRequest* request = [objectManager requestWithObject:nil method:RKRequestMethodPOST path:SESSION_URL parameters:session_params];
RKObjectRequestOperation* operation = [objectManager
objectRequestOperationWithRequest:request success:^(RKObjectRequestOperation* operation, RKMappingResult* result)
{
NSLog(#"RESULT [%#]", result);
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR [%#]", error);
}];
[operation start];
I think the most irritating thing is that the stuff I need is contained in the NSLocalizedRecoverySuggestion value. It is a session key I require.
OUTPUT:
E restkit.network:RKObjectRequestOperation.m:547 Object request failed: Underlying HTTP request operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1016 "Expected content type {(
"application/x-www-form-urlencoded",
"application/json"
)}, got text/html" UserInfo=0x1c52aed0 {NSLocalizedRecoverySuggestion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCbG8uUmVnQWxlcnQuQnJva2VyIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdC9CbG8uUmVnQWxlcnQuQVBJL2FwaSIsIm5iZiI6MTM5MjY0MTY2MSwiZXhwIjoxMzkyNjQ1MjYxLCJ1bmlxdWVfbmFtZSI6IkJ1dHRvbnMiLCJyb2xlIjoiUmVnQWxlcnRDb25zdW1lciJ9.JCTMGJRKlOxEtNrcGodpce-tqsRS4zlApNisKQW6iSw, AFNetworkingOperationFailingURLRequestErrorKey=, NSErrorFailingURLKey=http://..., NSLocalizedDescription=Expected content type {(
"application/x-www-form-urlencoded",
"application/json"
)}, got text/html, AFNetworkingOperationFailingURLResponseErrorKey=}
2014-02-17 14:54:20.808 AppName[5600:6403] E restkit.network:RKObjectRequestOperation.m:213 POST 'http://...' (200 OK / 0 objects) [request=0.0000s mapping=0.0000s total=0.1925s]: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1016 "Expected content type {(
"application/x-www-form-urlencoded",
"application/json"
)}, got text/html" UserInfo=0x1c52aed0 {NSLocalizedRecoverySuggestion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCbG8uUmVnQWxlcnQuQnJva2VyIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdC9CbG8uUmVnQWxlcnQuQVBJL2FwaSIsIm5iZiI6MTM5MjY0MTY2MSwiZXhwIjoxMzkyNjQ1MjYxLCJ1bmlxdWVfbmFtZSI6IkJ1dHRvbnMiLCJyb2xlIjoiUmVnQWxlcnRDb25zdW1lciJ9.JCTMGJRKlOxEtNrcGodpce-tqsRS4zlApNisKQW6iSw, AFNetworkingOperationFailingURLRequestErrorKey=, NSErrorFailingURLKey=http://..., NSLocalizedDescription=Expected content type {(
"application/x-www-form-urlencoded",
"application/json"
)}, got text/html, AFNetworkingOperationFailingURLResponseErrorKey=}
CODE THAT WORKED
Thanks to Wain for pointing me on the correct path there. I am a little disappointed that RestKit cannot handle such a simple request, and I need RestKit because this is just a session token to calling the other methods, but whatever works I guess:
NSDictionary* session_params = #{SESSION_USERNAME_KEY:SESSION_USERNAME_VALUE, SESSION_PASSWORD_KEY:SESSION_PASSWORD_VALUE};
NSURL* url = [NSURL URLWithString:SESSION_URL];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:SESSION_URL parameters:session_params];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString* response = [operation responseString];
NSLog(#"response: %#",response);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", [operation error]);
}];
[operation start];
This bit:
"Expected content type {( "application/x-www-form-urlencoded", "application/json" )}, got text/html"
tells you that you have told RestKit to expect form-urlencoded or json, but that the server is returning html.
You would probably want to use setAcceptHeaderWithMIMEType with JSON mime type to tell the server what you want back. But, in this case you probably just shouldn't be using RestKit.
RestKit is for mapping arbitrary JSON / XML data into your data model. You just have a key coming back. No mapping is required. So, don't use RestKit, use AFNetworking instead (which you have full access to because RestKit uses it internally.
Thanks to Wain and Quintin, this was quite useful to me :)
I think some names changed in more recent versions of Restkit or AFNetworking. I used AFNetworking as explained in other answers since the server did not return json but empty plain/text instead. This was only on a particular endpoint where I was looking for a token in the headers of the response.
Sharing my piece of code here too:
-(void) find_some_token_with_success:(void (^)(AFRKHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFRKHTTPRequestOperation *operation, NSError *error))failure {
NSURL *baseURL = [NSURL URLWithString:#"https://example.com"];
AFRKHTTPClient *client = [AFRKHTTPClient clientWithBaseURL:baseURL];
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
[client setDefaultHeader:#"some_custom_header" value:#"some_custom_value"];
NSMutableURLRequest *request = [client requestWithMethod:#"GET" path:#"/api/v1/some_non_json_endpoint" parameters:nil];
AFRKHTTPRequestOperation *operation = [[AFRKHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:success failure:failure];
[operation start];
}
Then I used something like this to get the header I was looking for:
-(void) get_the_token:(void (^)(NSString *token))withTokenCallback failure:(void (^)(AFRKHTTPRequestOperation *operation, NSError *error))failure {
[self xsrftoken_with_success:^(AFRKHTTPRequestOperation *operation, id responseObject) {
NSString *token = [self get_the_token_from_response:[operation response]];
withTokenCallback(token);
} failure:failure];
}
-(NSString *) get_the_token_from_response: (NSHTTPURLResponse *) response;
{
NSDictionary *headerDictionary = response.allHeaderFields;
NSString *token = [headerDictionary objectForKey:#"SOME-TOKEN-KEY"];
return token;
}
So all of this can simply be used like this:
- (void)testGetSometokenInARequest
{
XCTestExpectation *expectation = [self expectationWithDescription:#"Query timed out."];
[[SomeRequestWithoutJsonResponse alloc]
get_the_token:^(NSString *token) {
[expectation fulfill];
NSLog(#"token: %#", token);
// this token should be 100 characters long
XCTAssertTrue([token length] == 100);
}
failure:^(AFRKHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", [operation error]);
}];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}
In other words, get_the_token takes a callback with the desired token and a failure callback.
Make sure you still include <RestKit/RestKit> so you have access to Restkit's AFNetowkring :)
Alternative working solution using restkit:
RestKit: How to handle empty response.body?
And you register a serializer for that kind of Mimetype like this:
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"text/plain"];
This is the method I use to do HTTP GET request. How can I change it to use HTTPS?
AS_NET_BASE_URL = http://www.myapp.com
Is it just as simple as replacing the http:// with https:// ?
Thank you in advance
+(void)startGETRequestAtUrlRoute:(NSString *)route withParameters:(NSString *)slashSeparatedParams completion:(void (^)(BOOL, id))completion{
//If internet is rachable, start request
if ([self isInternetReachable]) {
//Where request is going
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:AS_NET_BASE_URL]];
NSString *path = [NSString stringWithFormat:#"%#%#%#", AS_NET_BASE_URL, route, slashSeparatedParams];
//Tell operation to expect JSON
[httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[httpClient setDefaultHeader:#"Accept" value:#"application/json"];
//Start spinner
[self startNetworkIndicator];
//Set up actual GET request
[httpClient getPath:path
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Stop spinning
[self stopNetworkIndicator];
//Make the response JSON valid
if (responseObject) {
completion(YES, responseObject);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self stopNetworkIndicator];
completion(NO, NULL);
//Error
NSLog(#"%#", error);
}
];
//No internet connection
}else{
completion(NO, NULL);
}
}
Yes.
HTTP/HTTPS is determined by the scheme of the URL, which in your case is specified in AS_NET_BASE_URL.
Problem
My app lets users upload photos. This works great.
Now, I am trying to implement a "retry" function if the photo upload fails, for example due to a slow connection.
Here's my retry code:
self.operation = [self.operation copy]; // Creates a new operation with the same NSURLRequest
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// do success stuff
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog("%#", error);
}];
[[MyAFHTTPClient sharedClient] enqueueHTTPRequestOperation:self.operation];
Upon starting, the failure block is called, outputting:
$0 = 0x12636b50 Error Domain=NSURLErrorDomain Code=-1021 "request body stream exhausted" UserInfo=0x12637810 {NSErrorFailingURLStringKey=https://my/long/url/, NSErrorFailingURLKey=https://my/long/url/, NSLocalizedDescription=request body stream exhausted, NSUnderlyingError=0x13046bb0 "request body stream exhausted"}
Question
How do I change my code to restart the image upload correctly?
What I've tried
I think the issue is that operation.request.HTTPBodyStream is an NSInputStream, which cannot be restarted.
The method -[AFURLConnectionOperation connection:needNewBodyStream:] appears to provide a copy of the input stream. I set a breakpoint in there; it's not called when copying or starting the operation, and I'm not sure how to trigger it.
There's some discussion on a similar issue on the AFNetworking GitHub page, but that relates to retrying after authentication failure.
Other info
My URL Request object is created using -[AFHTTPClient multipartFormRequestWithMethod:
path:
parameters:
constructingBodyWithBlock:]
I would try something like this :
-(void)uploadImage:(NSData *)imageData retry:(BOOL)retry
{
AFHTTPClient *myClient = [[AFHTTPClient alloc] initWithBaseUrl:myBaseURL];
NSURLRequest *request = [myClient multipartFormRequestWithMethod:#"POST"
path:myPath
parameters:myParametersDictionary
constructingBodyWithBlock:^(id <AFMultipartFormData> formData){
[formData appendPartWithFileData:imageData
name:myImageName
fileName:myFileName
mimeType:#"image/jpg"];
}];
AFHTTPRequestOperation *operation = [myClient HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// do success stuff
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog("%#", error);
if (retry) {
[self uploadImage:imageData
retry:NO];
}
}];
[myClient enqueueHTTPRequestOperation:operation];
}
Of course the first time you would call it with retry:YES