Sorry, a beginner's question: I have a very simple function in my Django application with which I can upload a file from a web browser to my server (works perfectly!). Now, instead of the web browser, I would like to use an iPhone.
I got a bit stuck as I don't really know how to provide Django with a valid form, i.e. we need a file name and enctype="multipart/form-data" as far as I understand.
Here is my upload function in Django:
class UploadFileForm(forms.Form):
file = forms.FileField()
def handle_uploaded_file(f):
destination = open('uploads/example.txt', 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
def upload_file(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
handle_uploaded_file(request.FILES['file'])
print form
print request.FILES
return HttpResponse('Upload Successful')
else:
form = UploadFileForm()
return render_to_response('upload.html', {'form': form})
My template looks like this (upload.html):
<form action="" method="post" enctype="multipart/form-data">
{{ form.file }}
{{ form.non_field_errors }}
<input type="submit" value="Upload" />
</form>
Now, let's suppose that I want to send a simple txt file from my iPhone app to the sever.
I don't really know how to:
provide the file name
specify the enctype and
make sure that it's in a format Django can read
This is how far I got:
NSString *fileContents = [self loadTXTFromDisk:#"example.txt"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:[NSURL
URLWithString:#"http://127.0.0.1:8000/uploadfile/"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"text/xml" forHTTPHeaderField:#"Content-type"];
[request setValue:[NSString stringWithFormat:#"%d", [fileContents length]]
forHTTPHeaderField:#"Content-length"];
[request setHTTPBody:[fileContents dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *theConnection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self];
However, Django will not except this as the form it expects is not valid. Cf. above:
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid(): #this will not be true ...
The enctype from the HTML form should be the Content-Type of the HTTP request. You are currently setting the content type to 'text/xml' instead.
You will also have to build up the body as a multipart mime object. The answers to this question seem to have some code for that: File Upload to HTTP server in iphone programming
Your other option, since you have complete control of the client, is to do an HTTP PUT request. That actually looks closer to what you were doing with your original code. Change the method to 'PUT', and don't use a Django form on the other end; just access request.raw_post_data to get the complete file data.
Related
I have an API which is used to delete a record in server DB. I used to construct the API with the request ID .It was working with CURL, but in Restkit it seems to give an error.
The Curl is:
curl -d '{eve:{mod_policy:"current"}}' -X DELETE -H Content-Type:application/json https://myurl.com/eve/eve_id?token=my_aut_token\&apikey=myapi_key.
I checked with POST & PATCH. It takes the JSON as a correct form.
My RestKit Code Sample:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{ #"modPolicy" : #"mod_policy"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[Event class] rootKeyPath:#"eve"];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Events class]];
[responseMapping addAttributeMappingsFromDictionary:#{
#"data" : #"data",
#"status":#"status"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping pathPattern:nil keyPath:#"" statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addRequestDescriptor:requestDescriptor];
[objectManager addResponseDescriptor:responseDescriptor];
NSString * urlPath = [NSString stringWithFormat:#"/eve/%#?token=%#&apikey=%#",eventID,loginToken,apiKey];
[objectManager deleteObject:hubEve path:urlPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *result)
{
DLog(#" response code is %d",operation.HTTPRequestOperation.response.statusCode);
Events * _event = [result firstObject];
DLog(#"status %#",_event.status);
if([_eventt.status isEqualToString:#"success"])
{
DLog("Move Next");
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
DLog("error %#",error);
}];
Some log details, if I send As DeleteObject in request:
request.body=(null) //Restkit Log
Or if I send as post Object/Patch Object
request.body={"eve":{"mod_policy":"all"}} //Restkit Log
Request mapping is explicitly not performed for DELETE requests. RestKit expects that when deleting you will be using the system to add parameters into the URL. You will need to plan some other method by which to delete. This could be using the RestKit mapping operation to create the payload data and then using the methods to create the URL request and setting the body data explicitly.
RESTKit does not support DELETE request with request.body parameter
natively because HTTP 1.1 doesn't support DELETE request with
request.body. There is a work around to set request.body explicitly but
its complex.
The request works well with cURL but not with HTTP, maybe because cURL
does not send DELETE request with request.body as DELETE but upgrades it
to PUT, we are not sure though.
I'm using a firefox tool call "Poster" to check that my web service is working. When I POST the request I send a JSON object in the content to send (also call HTTP body data).
When I press the POST button the result is fine.
Now I want to do exactly the same with RestKit in objective-c:
NSString *squery = #"{\"Sort\":\"Relevance\",\"DaysToSearch\":0,\"WorkType\":\"\",\"PageSize\":20,\"LastRunCount\":0,\"IndustryCodes\":[\"\"],\"AccountId\":27,\"Experience\":\"\",\"GetResultCount\":0,\"Keywords\":\"iOS\",\"PageIndex\":0,\"DistanceFromLocation\":\"250\",\"SalaryType\":\"\",\"JobQueryId\":\"\",\"JobTitleCodes\":[\"\"]}";
RKObjectLoader *objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:url delegate:performJobQueryHandler];
objectLoader.method = RKRequestMethodPOST;
objectLoader.objectMapping = s.jobQueryDataMapper;
objectLoader.HTTPBody = [squery dataUsingEncoding:NSUTF8StringEncoding];
objectLoader.serializationMIMEType = RKMIMETypeJSON;
[objectLoader send];
As a result I get: "An non-fault exception is occured."
In the FireFox extension "Poster" if I use a different mine-type than "application/json", let say "application/x-www-form-urlencoded" I get the same error: "An non-fault exception is occured."
Is something wrong with objectLoader.serializationMIMEType = RKMIMETypeJSON; ???
Martin Magakian
Ok I think I find out.
I activated RestKit debug with
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
And I get:
2012-02-14 11:55:48 [4048:207] T restkit.network:RKRequest.m:318 Prepared POST URLRequest '<NSMutableURLRequest http://servicestest.foo.com/jobservice/jobservice.svc/json/jobquery>'. HTTP Headers: {
Accept = "application/json";
"Content-Type" = "application/x-www-form-urlencoded";
}. HTTP Body: Sort=Relevance&DaysToSearch=0&WorkType=&SalaryType=&LastRunCount=0&IndustryCodes[]=&Experience=&AccountId=0&GetResultCount=0&PageIndex=0&DistanceFromLocation=250&PageSize=20&JobQueryId=&JobTitleCodes[]=.
So I think using RKObjectLoader object don't care about objectLoader.serializationMIMEType = RKMIMETypeJSON; because the Content-Type remain "Content-Type" = "application/x-www-form-urlencoded"; instead of "Content-Type" = "application/json";
Do you think it's a bug or the normal behavior ?
I am using ASIHTTPRequest to contact an API the problem is that it is returning html instead of JSON and i have no idea why. Everything has been working great for weeks and i can't seem to figure out what has broken..
What's strange is doing exactly the same request with a browser or postie returns the correct JSON response, but when i do the request via ASIHTTPRequest these are the response headers i get:
"Cache-Control" = "private, max-age=0, must-revalidate";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Type" = "text/html; charset=utf-8";
Date = "Tue, 05 Jul 2011 21:11:10 GMT";
Etag = "\"e467792713ac4124f055c1719f4ea6c2\"";
Server = "nginx/0.7.65";
Status = "200 OK";
"Transfer-Encoding" = Identity;
"X-Runtime" = 1;
And the html (which should be json)..
<div id="universal_meta" logged_in_user_id="removed" style="display:none"></div>
<div id="meta" screen="projects" logged_in_as="removed"></div>
<h1>9 projects with 50 open issues</h1>
<ul class="biglinks progressbars">
etc...
Here is the code used to do the request..
NSURL *url = [NSURL URLWithString:path];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
if(secure){
[request setUsername:self.username];
[request setPassword:self.password];
}
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[request setDownloadDestinationPath:destination];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
I have triple checked all of the login details/url, everything is correct, i would really appreciate any help with this one as i am completely lost.
--
Update:
Lighthouse have acknowledged a bug regarding headers on their side. Waiting on a solution from them.
Did you check that the headers sent using your browser are also the same as when you send the request by code?
Especially did you try to add the "Accept" header to your request to define that you accept the json Content-Type?
[request addRequestHeader:#"Accept" value:#"application/json"];
Try using a tool like wireshark or charlesproxy to compare the working requests from your browser/postie, and compare them to the non-working requests from ASIHTTPRequest - once you spot what's different it should be easy to fix.
AliSoftware's suggestion is one of the most likely differences you'll see, but it's possible there are other things too.
i want to send JSON with POST/PUT i don't know if it's the same think .
This is the json .
{"author":"mehdi","email":"email#hotmail.fr","message":"Hello2"}
this is my wadl .
<resource path="/messages/"><method id="get" name="GET"><request><param name="start" style="query" type="xs:int" default="0"/><param name="max" style="query" type="xs:int" default="10"/><param name="expandLevel" style="query" type="xs:int" default="1"/><param name="query" style="query" type="xs:string" default="SELECT e FROM Message e"/></request><response><representation mediaType="application/xml"/><representation mediaType="application/json"/></response></method><method id="post" name="POST"><request><representation mediaType="application/xml"/><representation mediaType="application/json"/></request><response><representation mediaType="*/*"/></response></method><resource path="{keyid}/"><param name="keyid" style="template" type="xs:int"/><method id="get" name="GET"><request><param name="expandLevel" style="query" type="xs:int" default="1"/></request><response><representation mediaType="application/json"/></response></method></resource></resource>
When i trie to post with netbeans "Test restful webservice " it work and this is wat the http monitor show
Status: 201 (Created)
Time-Stamp: Sat, 21 May 2011 20:30:33
GMT
Sent:
{"author":"mehdi","email":"email#hotmail.fr","message":"Hello2"}
Received:
Request: POST
http://localhost:8080/TRESTful/resources/messages/?
timestamp=1306009225785
Status: 201 (Created)
Time-Stamp: Sat, 21 May 2011 20:20:25
GMT
Sent:
{"author":"mehdi","email":"email#hotmail.fr","message":"Hello2"}
But now i dont know how it with ASIHttpRequest .first should i make [request setRequestMethod:#"PUT"]; ? and should i send all the json like a string , or put each value with hey key , like this :
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"Ben" forKey:#"author"];
[request setPostValue:#"Copsey" forKey:#"email"];
[request setPostValue:#"Hello2" message:#"author"];
[request startSynchronous];
or should i put the JSON string in one value ?
Help please .
Well if you want to send a json you have two options:
a) Send it in a file
-Use this if you have a lot of data in a json and you may want to store it on the server for later parsing.
b) Send it as string in one value
-Use this if your json is short and you want to parse it immediately
EDIT
c) Send it as seperate values if you want to skip parsing on the server and access data directly. This should be used if there is few data.
NSString *urlString = #"http://172.29.165.219:8090/abc/services/Abc";
NSString *soapMessage = [NSString stringWithFormat:
#"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
"<soap:Body>\n"
"<getUserInfo>\n"
"<email>%#</email>\n"
"<pwd>%#</pwd>\n"
"</getUserInfo>\n"
"</soap:Body>\n"
"</soap:Envelope>\n", #"babla.sharan#tcs.com",#"UGFzc0AxMjM="//,#"50006F0063006B0065007400500043000000-444556494345454D00",#"PocketPC"
];
NSMutableURLRequest *loginRequest = [RequestGenerator -generateSOAPRequestWithURLString:urlString soapMessage:soapMessage contentType:#"text/xml; charset=utf-8" action:#"getUserInfo"];
WebServiceHandler *connection = [[WebServiceHandler alloc]init];
For the Above code, it works fine, but when I add additional Tag as parent tag to id and password
NSString *urlString = #"http://172.29.165.219:8090/abc/services/Abc";
NSString *soapMessage = [NSString stringWithFormat:
#"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
"<soap:Body>\n"
"<getUserInfo>\n"
**"<ABC>\n"**
"<email>%#</email>\n"
"<pwd>%#</pwd>\n"
**"</ABC>\n"**
"</getUserInfo>\n"
"</soap:Body>\n"
"</soap:Envelope>\n", #"babla.sharan#tcs.com",#"UGFzc0AxMjM="//,#"50006F0063006B0065007400500043000000-444556494345454D00",#"PocketPC"
];
NSMutableURLRequest *loginRequest = [RequestGenerator generateSOAPRequestWithURLString:urlString soapMessage:soapMessage contentType:#"text/xml; charset=utf-8" action:#"getUserInfo"];
WebServiceHandler *connection = [[WebServiceHandler alloc]init];
It shows exception and get this error:
org.xml.sax.SAXException: SimpleDeserializer encountered a child element, which is NOT expected, in something it was trying to deserialize.
It doesn't even allow me to hit the server.
Here in second soap message you are sending an additional parent tag for you information, which may not be recognized by you server. If this tag is optional than there should be no problem at all, but if it is undesired then you should not use it. Below are the excerpts from W3.org(http://www.w3.org/TR/2000/NOTE-SOAP-20000508/) .... which tells about the receiving and processing of soap request.
A SOAP application receiving a SOAP message MUST process that message by performing
the following actions in the order listed below:
1. Identify all parts of the SOAP message intended for that application
2. Verify that all mandatory parts identified in step 1 are supported by
the application for this message (see section 4.2.3) and process them accordingly.
If this is not the case then discard the message .
The processor MAY ignore optional parts identified in step 1 without affecting the outcome of the processing.
3. If the SOAP application is not the ultimate destination of the message then
remove all parts identified in step 1 before forwarding the message.
Processing a message or a part of a message requires that the SOAP processor
understands, among other things, the exchange pattern being used (one way,
request/response, multicast, etc.), the role of the recipient in that pattern, the
employment (if any) of RPC mechanisms such as the one documented in section 7, the
representation or encoding of data, as well as other semantics necessary for correct
processing