json response is interpreted as text/plain - restkit

I have the following code:
NSURL *URL = [NSURL URLWithString:[#"some-address"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:[self.objectManager responseDescriptors]];
[requestOperation start];
[requestOperation waitUntilFinished];
I get the following error.
Object request failed: Underlying HTTP request operation failed with error: Error Domain=AFNetworkingErrorDomain Code=-1016 "Expected content type {(
    "application/x-www-form-urlencoded",
    "application/json"
)}, got text/plain" UserInfo=0x1f5e3c40 {NSLocalizedRecoverySuggestion={"total_rows":16,"offset":1,"rows":[
{"id":"1","key":1,"value":{"_id":"1","_rev":"1-e75042683867a7030fc4d3aa3b72ef35",
"user":{
"userId":"1",
"name":"A",
.......
]}}, .....
Why I get this error, when the response is in Json format?

we did it. just set
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"text/plain"];
and feel free to change RKXMLReaderSerialization class with RKNSJSONSerialization class if you're using JSON instead of XML (XML was our case).

You didn't set the mime-type header properly in your response. Note the error says got text/plain, while the code is expecting either application/json or application/x-www-form-urlencoded.

Related

Why must I send the CSRF token with AFNetworking but not with cURL?

When I perform a POST request via cURL, it completes successfully (HTTP 201) even without sending the CSRF token header.
curl -X POST http://server/app/addrecord/ -d '{"pk":"1", "lname":"Smith", "fname":"Robert"}' -H "Content-Type: application/json" > out.html
However when I attempt the same operation using AFNetworking in Obj-C, I get a 403 error that states the CSRF token is required.
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager setRequestSerializer:serializer];
AFJSONResponseSerializer *respSerializer = [AFJSONResponseSerializer serializer];
respSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/html", #"text/plain", #"text/json", nil];
manager.responseSerializer = respSerializer;
[manager POST:#"http://server/app/addrecord/" parameters:parameters success:^(NSURLSessionDataTask * task, id responseObject) {
NSLog(#"Success");
} failure:^(NSURLSessionDataTask * task, NSError * error) {
NSLog(#"Error");
}];
Simply by adding the following line to my request serializer, the error goes away.
[serializer setValue:csrf forHTTPHeaderField:#"X-CSRFTOKEN"];
While I don't need the AFNetworking code to work without the CSRF Token being sent, I would like to understand why the cURL command works without the token but the AFNetworking code doesn't.

AFNetworking and Authorization Header

I'm new to AFNetworking, and I'm trying to use it to talk to an API that I've written in Go. I'm having difficulty getting the Authorization header to work. I've subclassed AFHTTPSessionManager and configured it as follows
+ (HMAPIClient *)sharedHMAPIClient
{
static HMAPIClient* _sharedHMAPIClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedHMAPIClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:HMBaseURL]];
});
return _sharedHMAPIClient;
}
- (instancetype)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.requestSerializer = [AFJSONRequestSerializer serializer];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:RegistrationAPIKey
password:#"Doesn't matter what goes here."];
}
return self;
}
- (void)hitTestEndpoint
{
[self GET:#"testEndpoint" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"%#", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", error);
}];
}
When I call -(void)hitTestEndpoint, I see the following headers in my server logs (Authorization is missing):
Key: Accept-Encoding Value: [gzip, deflate]
Key: Connection Value: [keep-alive]
Key: Accept-Language Value: [en;q=1]
Key: User-Agent Value: [TestApp/2 (iPad Simulator; iOS 8.1; Scale/2.00)]
Key: Accept Value: [*/*]
For comparison, when I hit the same endpoint with the following curl command,
curl https://api.example.com/v1/testEndpoint/ -u test_xqzwjcasogptbnpa:
I see the following headers:
Key: Authorization Value: [Basic eHF6d2pjYXNvZ3B0Ym5wYTo=]
Key: User-Agent Value: [curl/7.30.0]
Key: Accept Value: [*/*]
Can someone point me in the right direction? -Thanks
Update:
I have added AFNetworkActivityLogger so that I can see each request. The Authorization header is indeed included. Also, I tried hitting http://headers.jsontest.com, which returns the HTTP request headers received from the client. The Authorization header is present in that output.
So, the problem must be with my server. I'm already logging all headers for each request, and I'm not sure where else to look. Going to tag this question with Go to see if someone has an idea.
Update 2:
I added a call to httputil.DumpRequest at the top of my request handler, and it also shows that the Authorization header is missing. By the way, any custom headers that I set do appear as expected. It's just the Authorization header that's missing.
Here's the Go Code:
func testResponse(rw http.ResponseWriter, request *http.Request) {
// check output from DumpRequest()
dump,err := httputil.DumpRequest(request,true)
check(err)
fmt.Println("Output of DumpRequest():")
fmt.Println(string(dump))
fmt.Println("============")
fmt.Println("request.Headers:")
for key, value := range request.Header {
fmt.Println("Key:", key, "Value:", value)
}
fmt.Println("===============")
// return some dummy JSON
rw.Header().Set("Content-Type", "application/json")
rw.Write(PersonToJson(getPerson("2f6251b8-d7c4-400f-a91f-51e09b8bfaf4")))
}
The server log you're showing looks like the headers after Go has already parsed them. It would be helpful to see the raw, plaintext HTTP headers that Go received. That would tell you if Go is ignoring the header or if something upstream is stripping it out.
Edit: Not sure why Go would strip out the Authorization header before giving you the supposedly raw request. But I think the Authorization header is normally sent by the client only after making a previous un-authorized request and getting a 401 response from the server with a WWW-Authenticate header. Since it sounds like your client is sending the Authorization header out of the blue, maybe the Go server API is ignoring & stripping the header because it never asked the client to send it.
If you just want to send a simple auth token on every request, what if you simply used a made up X- header instead, since you indicated that other headers you set arrive just fine?

get custom headers of AFHTTPRequestOperation

I attempt to get custom header key using AFHTTPRequestOperation, I try using allHeaderFields but nothing, here is header response
HTTP/1.1 302 Moved Temporarily
Server nginx
Date Tue, 19 Feb 2013 16:38:29 GMT
Content-Type text/html
Transfer-Encoding chunked
Connection keep-alive
Set-Cookie AUTH-ID="fjArrnmlyNMU9kfIu38Oc0LS451Y/UaMn0rb5sKj46CxmfJj8y8yr8CfwOewItFY"; HTTPOnly
X-AUTH-TOKEN mfy+426BNZdq1h92As3oXdZbf2iOI7wV7EOEUMAV3hAqtY7cOnWvA4df7h6RfjeD
Location /home.php
I use AFHTTPRequestOperation like this
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *headerData = [[operation response] allHeaderFields];
if ([headerData objectForKey:#"X-AUTH-TOKEN"] != nil)
token = [headerData objectForKey:#"X-AUTH-TOKEN"];
NSLog(#"headers = %#", headerData);
NSLog(#"token = %#", token);
...
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
...
}];
but X-AUTH-TOKEN key don't appear in headers NSLog, I'm sure the header key is present because I use Charles proxy to debug and Charles show me the X-AUTH-TOKEN key. Maybe the 302 status code is the problem, can anyone help me please?
Thanks.
[EDIT] when I try to show status code of response, it's 200.
Solved using some tweaks of AFURLConnectionOperation for intercept redirect response.

multipart POST request directly in ASIFormDataRequest

So you think it would be easy to find a direct example of POST information to ASIFormDataRequest, but I havent found a 1:1 in these formats. Here's the POST request I want to send:
POST /fileXfer HTTP/1.1
Content-Type: multipart/form-data; boundary=AaB03x
Content-Length: 200
AppID:myID
TransferID:abcd1234
-- AaB03x
Content-Disposition: form-data; name="data"; filename="xxx"
Content-Type: application/octet-stream
Content-Length: 100
... contents of file ...
--AaB03x--
EDIT:
Turns out my problems were more with the server receiving the data. But this is what I ended up with!
NSURL *url = [NSURL URLWithString:#"http://IP:PORT/fileXfer"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostFormat:ASIMultipartFormDataPostFormat];
//Sets headers of POST
[request addRequestHeader:#"X-Application-Id" value:#"myID"];
[request addRequestHeader:#"X-Transfer-Id" value:#"abcd1234"];
//Sets data
NSData *fileData = [NSData dataWithContentsOfFile:kEncryptedFilePath];
[request setData:fileData withFileName:#"xxx" andContentType:#"application/octet-stream" forKey:#"data"];
[request setDelegate:self];
[request startAsynchronous];
I also added the following line to ASIFormDataRequest's buildMultipartFormDataPostBody where Content-Disposition and Content-Type are set:
[self appendPostString:[NSString stringWithFormat:#"Content-Length: %lu\r\n\r\n", [data length]]];`
I'm not sure if there's an easier way to add that line directly from the request, but out of the things I tried, this seemed to be the one that worked.
Rather than building the HTTP body manually, which may lead to errors, you should better use the methods provided by ASIFormDataRequest.
NSURL *url = [NSURL URLWithString:#"http://IP:PORT/fileXfer"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request addRequestHeader:#"X-Application-Id" value:#"myID"];
[request addRequestHeader:#"X-Transfer-Id" value:#"abcd1234"];
[request setData:[self fileData] withFileName:#"xxx" andContentType:#"application/octet-stream" forKey:#"data"];
[request setDelegate:self];
[request startAsynchronous];

Is this a mal-formed http request from an iPad, which kills Node.js multipart parser

The below code is used in an iPad app to send an HTTP request to a Node.js web server, which produces the following error, but works fine w/ a regular HTML+browser form.
The server is Node.js + formidable which has a multipart parser that only dies on this line of code with this error:
message: parser error, 0 of 29162
bytes parsed
stack: Error: parser error, 0 of 29162
bytes parsed
at IncomingForm.write (/usr/local/lib/node/.npm/formidable/0.9.8/package/lib/formidable/incoming_form.js:120:17)
at IncomingMessage. (/usr/local/lib/node/.npm/formidable/0.9.8/package/lib/formidable/incoming_form.js:73:12)
at IncomingMessage.emit (events:27:15)
at HTTPParser.onBody (http:100:23)
at Stream.ondata (http:763:22)
at IOWatcher.callback (net:494:29)
at node.js:768:9
This is the iPad code:
NSMutableURLRequest * theRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[theRequest setTimeoutInterval:60];
[theRequest setHTTPMethod:#"POST"];
NSString *boundary = [NSString stringWithString:#"---------------------------14737809831466499882746641449"];
NSString *contentType = [NSString stringWithFormat:#"multipart/form-data; boundary=%#",boundary];
[theRequest addValue:contentType forHTTPHeaderField: #"Content-Type"];
NSMutableData *body = [NSMutableData data];
//media
[body appendData:[[NSString stringWithFormat:#"\r\n--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:#"Content-Disposition: form-data; name=\"image\"; filename=\"iosaudio.cai\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:#"Content-Type: image/jpeg\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:theAudio]];
[body appendData:[[NSString stringWithFormat:#"\r\n--%#--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the reqeust
[theRequest setHTTPBody:body];
Is the request being sent malformed? If so, why and how should it be done?
We actually just had this same problem in some iOS code posting to Node.js. Our issue turned out to be the CR-LF preceding the first boundary. Node.js uses a component for parsing MIME that is very picky about the format and the preceding CR-LF characters are viewed as malformed. I'm not sure, but the CR-LF following your last boundary may cause the same issue. Your first boundary should look like this:
[body appendData:[[NSString stringWithFormat:#"--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
Your final boundary should look like this:
[body appendData:[[NSString stringWithFormat:#"\r\n--%#--",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
Note that intervening boundaries should include the CR-LF before and after the boundary:
[body appendData:[[NSString stringWithFormat:#"\r\n--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
My read of the specification indicates you can have data before the first boundary and after the final boundary, but the parser should ignore these areas. The component Node.js is using to parse this is being very picky. There is a patch to the parsing component, but it hasn't made its way into the project yet.

Resources