简体   繁体   中英

ios text upload via multipart HTTP request

I am trying to upload a text using multi-part form encoding in iOS.

What i Did :

I have tried via the info provided in the ios Upload Image and Text using HTTP POST .

Problem :

I put log in server side, And here is what did i receive from my IOS App.

No problem with key Name. But, I receive the value as null

"443" "POST" "/api/private/json/" "content=null&scope=null&action=null&documentname=null&apikey=null"

My code :

    NSMutableDictionary *params = [NSMutableDictionary new];
    [params setObject:@"XXXXX" forKey:@"apikey"];
    [params setObject:@"DataAPI" forKey:@"scope"];
    [params setObject:@"push" forKey:@"action"];
    [params setObject:docName forKey:@"documentName"];
    [params setObject:content forKey:@"content"];
    [params setObject:authToken forKey:@"authtoken"];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    [request setHTTPShouldHandleCookies:NO];
    [request setTimeoutInterval:30];
    [request setHTTPMethod:@"POST"];

    // set Content-Type in HTTP header
    NSString *boundary = @"V2ymHFg03ehbqgZCaKO6jy";

    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];

    NSMutableData *body = [NSMutableData data];



    // add params (all params are strings)
    for (NSString *param in params) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", [params objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];

    }

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];


    // setting the body of the post to the reqeust
    [request setHTTPBody:body];
    NSString *myString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];


    NSLog(@"%@",myString);
    // set the content-length
    NSString *postLength = [NSString stringWithFormat:@"%d", [body length]];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

    // set URL
    [request setURL:url];


    NSURLResponse *response;
    NSData *POSTReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
    NSString *theReply = [[NSString alloc] initWithBytes:[POSTReply bytes] length:[POSTReply length] encoding: NSASCIIStringEncoding];
    NSLog(@"Reply: %@", theReply);

I couldn't able to understand the issue, Whether it is from server side or from my code.

Thanks for sharing your answers.

You are not appending the terminating boundary.

This:

[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

Should look like this:

[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

EDIT:

You are missing some more things actually. You are not setting the content type for the data and you're missing some new lines.

Your loop should look like this:

for (NSString *param in params) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", [params objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}
[body appendData:[[NSString stringWithFormat:@"\r\n\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

EDIT2:

Here's an example server-side code which is handling that same request correctly:

#!/usr/bin/env python

import web

urls = ("/", "Index")
app = web.application(urls, globals())

class Index(object):
    def POST(self):
        x = web.input()
        print x
        print x['apikey']
        print x['scope']
        return 'success'

if __name__ == "__main__":
    app.run()

Output:

<Storage {'scope': u'asd', 'apikey': u'123\r\n'}>
123

asd
127.0.0.1:60044 - - [02/Dec/2013 15:02:55] "HTTP/1.1 POST /" - 200 OK

And sample console app. which you can compile with clang - clang -framework Foundation test_post.m -o test_post

#import <Foundation/Foundation.h>

@interface Uploader : NSObject <NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSDictionary *parameters;
@property (nonatomic, strong) NSURLConnection *connection;

@end

@implementation Uploader

- (id)init {
    if (self = [super init]) {
    }
    return self;
}

- (NSMutableURLRequest*)createRequest {
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:@"http://localhost:8080/"]];
    [request setHTTPMethod:@"POST"];
    NSString *boundary = @"0xB0un9ArY";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"1.0" forHTTPHeaderField:@"MIME-Version"];
    [request setValue:@"keep-alive" forHTTPHeaderField:@"Connection"];

    NSMutableData *body = [NSMutableData data];

    for (NSString *key in [self.parameters allKeys]) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", (self.parameters)[key]] dataUsingEncoding:NSUTF8StringEncoding]];
    }
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [request setHTTPBody:body];

    return request;
}

- (void)upload {
    NSMutableURLRequest *request = [self createRequest];
    self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    if (self.connection) [self.connection start];
}

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {NSLog(@"connection:didReceiveResponse: %@", response);} 
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {NSLog(@"connection: didReceiveData: %@", data);} 
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {NSLog(@"connection: didFailWithError: %@", error);}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection { NSLog(@"connectionDidFinishLoading:"); }
@end

int main(int argc, char *argv[])
{
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

    Uploader *uploader = [[Uploader alloc] init];
    uploader.parameters = @{@"apikey": @"123", @"scope": @"asd"};
    [uploader upload];

    [runLoop run];
    }
    return 0;
}

This should fix it:

Instead of:

[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

write

[body appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

(the terminating CRLF is optional)

Please note if you write this:

for (NSString *param in params) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", [params objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}
[body appendData:[[NSString stringWithFormat:@"\r\n\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

that everything before the boundary-delimiter, eg \\r\\n--<boundary> conceptually belongs to the data , eg to the parameter value.

Likewise, the preceding CRLF of a "dash-boundary" ( --<boundary> ) belongs to the delimiter.

Conceptually, the first delimiter MUST also start with a CRLF:

`CRLF--<boundary>`

however, it seems NSURLConnection already puts a CRLF after the message headers. So we end up having this:

Content-Type: multipart/form-data; boundary="xyz"CRLF
<other header>CRLF
CRLF             <-- added by NSURLConnection, which is debatable if this is correct
... multipart body starts here

Any bytes after the headers and before the start of multipart shall be ignored by the server. This kind of junk is called preamble :

Content-Type: multipart/form-data; boundary="xyz"CRLF
<other header>CRLF
<preamble bytes>CRLF--xyz
                ^~~~~~~~~ delimiter
....

preamble may be empty or it may contain any number of CRLFs, for example.

The body-part , which consists of MIME headers and the part body-data, follows after the boundary, but strictly not immediately:

<preamble-bytes>CRLF--xyz<transport-padding>CRLF<body-part> 
                ^~~~~~~~~ delimiter             ^~~~~~~~~~~ MIME headers and data

where <transport-padding> is any number of spaces or horizontal tabs, that is, it may be empty.

This all means, that we should be able to use the following line as a non-terminating delimiter (for the first and any subsequent non-terminating boundary-delimiter) plus a CRLF:

[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

Then the body-part immediately follows (starting with headers, then the data):

[body appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@", (self.parameters)[key]] dataUsingEncoding:NSUTF8StringEncoding]];

These four lines can be used in a loop for each parameter.

The terminating delimiter can be added as follows:

[body appendData:[[NSString stringWithFormat:@"\r\n--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

A terminating CRLF is optional. Any bytes following the last CRLF belong to the epilogue and shall be ignored by the server.

See: RCF 2046 http://www.ietf.org/rfc/rfc2046.txt .

NSURL *url = [NSURL URLWithString:@"http://210.7.64.98/360videos/webservices/devicetoken.php"];
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:devToken1, @"token",
                          @"iphone", @"device_type", nil];
NSData *postData = [self encodeDictionary:postDict];

// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:[NSString stringWithFormat:@"%d", postData.length] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];




dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Peform the request
    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                 returningResponse:&response
                                                             error:&error];
    if (error) {
        // Deal with your error
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
            NSLog(@"HTTP Error: %d %@", httpResponse.statusCode, error);
            return;
        }
        NSLog(@"Error %@", error);
        return;
    }


    NSString *responeString = [[NSString alloc] initWithData:receivedData
                                                    encoding:NSUTF8StringEncoding];
    NSLog(@"token Response : %@",responeString);
});

//here is encode method

  • (NSData*)encodeDictionary:(NSDictionary*)dictionary { NSMutableArray *parts = [[NSMutableArray alloc] init]; for (NSString *key in dictionary) { NSString *encodedValue = [[dictionary objectForKey:key] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *encodedKey = [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *part = [NSString stringWithFormat: @"%@=%@", encodedKey, encodedValue]; [parts addObject:part]; } NSString *encodedDictionary = [parts componentsJoinedByString:@"&"]; return [encodedDictionary dataUsingEncoding:NSUTF8StringEncoding]; } //may be this will help full for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM