Hi want to send some data (strings and a file) to a server, by using AFNetworking 2.0. Somehow the data for the POST request (for a forumlar) ist not correct, it looks like that the encoding/serialization on the request is missing. As the server can't work with the uploaded data from me.
How do I set the encoding/serialization to the request?
I assume the URL Form Parameter Encoding, has to be set. The docs states
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
I tried to do that, but I cannot figure out how to do it right. With the following Xcode throughs a warning:
manager.requestSerializer = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
/.../CameraViewController.m:105:31: Incompatible pointer types assigning to 'AFHTTPRequestSerializer *' from 'NSMutableURLRequest *'
Below my sourcecode:
CameraViewController.h
#import <UIKit/UIKit.h>
@interface CameraViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
CameraViewControllerView.m
#import "CameraViewController.h"
#import "AFHTTPRequestOperationManager.h"
@interface CameraViewController ()
@property (nonatomic) int photoIsTaken;
@end
@implementation CameraViewController
// removed unecessary code for this question
- (void)upload {
NSLog(@"%s: uploader ", __FUNCTION__);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"latitude": @"8.444444",
@"longitude": @"50.44444",
@"location": @"New York",
@"type": @"2",
@"claim": @"NYC",
@"flag": @"0",
@"file": UIImageJPEGRepresentation(self.imageView.image,0.2)};
NSString *URLString = @"http://192.168.1.157/tapp/laravel/public/foobar/upload";
manager.requestSerializer = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
[manager POST:URLString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@, %@", error, operation.responseString);
}];
[self dismissViewControllerAnimated:NO completion:nil];
}
@end
Finally it works. Was a hassle but now I am really happy... During my testing I had some problems with 'request body stream exhausted' within Wifi, what was strange.
Below the code that did the trick for me.
- (void)upload {
// !!! only JPG, PNG not covered! Have to cover PNG as well
NSString *fileName = [NSString stringWithFormat:@"%ld%c%c.jpg", (long)[[NSDate date] timeIntervalSince1970], arc4random_uniform(26) + 'a', arc4random_uniform(26) + 'a'];
// NSLog(@"FileName == %@", fileName);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"lat": @"8.444444",
@"lng": @"50.44444",
@"location": @"New York",
@"type": @"2",
@"claim": @"NYC",
@"flag": @"0"};
// BASIC AUTH (if you need):
manager.securityPolicy.allowInvalidCertificates = YES;
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername:@"foo" password:@"bar"];
// BASIC AUTH END
NSString *URLString = @"http://192.168.1.157/tapp/laravel/public/foobar/upload";
/// !!! only jpg, have to cover png as well
NSData *imageData = UIImageJPEGRepresentation(self.imageView.image, 0.5); // image size ca. 50 KB
[manager POST:URLString parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData name:@"file" fileName:fileName mimeType:@"image/jpeg"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Failure %@, %@", error, operation.responseString);
}];
[self dismissViewControllerAnimated:NO completion:nil];
}
Thanks @NobleK , a category may be the best way to fix this issue. Here is a sample code:
@interface AFURLConnectionOperation (AuthenticationChallengeUploadFix)
@end
@implementation AFURLConnectionOperation (AuthenticationChallengeUploadFix)
- (NSInputStream *)connection:(NSURLConnection __unused *)connection needNewBodyStream:(NSURLRequest *)request {
if ([request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
return [request.HTTPBodyStream copy];
}
return nil;
}
@end
I have been looking for good answer for this one hell of a problem more than 10 hours and finally get hold of something what would work! according to Apple Doc
NSURLErrorRequestBodyStreamExhausted (-1021)
Returned when a body stream is needed but the client does not provide one. This impacts clients on iOS that send a POST request using a body stream but do not implement the NSURLConnection delegate method connection:needNewBodyStream.
so what I had to do is subclass AFHTTPRequestOperation and implement all the delegate methods for NSURLConnection
//.h
@interface CGHTTPRequestOperation : AFHTTPRequestOperation
@end
//.m
@implementation CGHTTPRequestOperation
#pragma mark NSURLConnection delegate methods
- (NSInputStream *)connection:(NSURLConnection __unused *)connection needNewBodyStream:(NSURLRequest *)request {
if ([request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
return [request.HTTPBodyStream copy];
}
return nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[super connection:connection didReceiveResponse:response];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[super connection:connection didReceiveData:data];
}
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
[super connection:connection didSendBodyData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse{
return [super connection:connection willCacheResponse:cachedResponse];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
[super connectionDidFinishLoading:connection];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[super connection:connection didFailWithError:error];
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {
return YES;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[super connection:connection willSendRequestForAuthenticationChallenge:challenge];
}
@end
If you wonder how to use this extended classes in regards to upload the multi-part image data here is the example
//.h
typedef enum {
CGFileUploadStatusError = 0,
CGFileUploadStatusSuccess = 1,
} CGFileUploadStatus;
typedef void(^CGNetworkingFileUploadCBlock) (CGFileUploadStatus status,NSString *responseString);
//.m
+ (void) uploadImageAtPath:(NSString *) imagePath cBlock:(CGNetworkingFileUploadCBlock) cBlock {
AFHTTPRequestSerializer *r = [AFHTTPRequestSerializer serializer];
NSDictionary *param = @{@"param1":@"",@"param2":@""};
NSData *d = [NSData dataWithContentsOfFile:imagePath];
__block NSString *paramNameForImage = [imagePath pathComponents].lastObject;
NSError *error = nil;
NSMutableURLRequest *urlRequest = [r multipartFormRequestWithMethod:@"POST" URLString:@"http://url_to_up_load_image" parameters:param constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:d name:@"FileUploadControl" fileName:paramNameForImage mimeType:@"image/jpeg"];
} error:&error];
if (error) {
NSLog(@"Some error:%@",error);
}
CGHTTPRequestOperation *requestOperation = [[CGHTTPRequestOperation alloc] initWithRequest:urlRequest];
//[requestOperation setCredential:nil]; //set cred here
//[requestOperation setSecurityPolicy:nil]; //set security policy here if you are using one
[requestOperation setResponseSerializer:[AFHTTPResponseSerializer serializer]];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success: %@ ***** %@", operation.responseString, operation.response.allHeaderFields);
cBlock(CGFileUploadStatusSuccess,operation.responseString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@ ***** %@", operation, error);
cBlock(CGFileUploadStatusError,operation.responseString);
}];
[requestOperation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
}];
[requestOperation start];
[requestOperation waitUntilFinished];
}
Hope this helps those who is suffering this problem :)