Search code examples
iphoneiosipadnsmutableurlrequest

Add fields to NSMutableURLRequest


How would I add key value pairs to the body of a NSMutableURLRequest? I know I could use something like this:

NSURL *url = [[NSURL alloc] initWithString:@"http://localhost:8080/upload/"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *bodyContents = @"fileName=";
[bodyContents stringByAppendingString:fileName];
[bodyContents stringByAppendingString:@"&"];
[bodyContents stringByAppendingString:@"deviceName="];
[bodyContents stringByAppendingString:deviceName];
[bodyContents stringByAppendingString:@"fileContents="];
[bodyContents stringByAppendingString:fileContents];

But that seems like a lot of hassle, especially if I get to where I need to submit a lot of values in a request. I would hope apple would provide a method like [request addBodyField:@"fileName":fileName] or something equally useful, but I haven't been able to find one in the documentation. Is there one I'm missing or should I just stick to appending strings?


Solution

  • Lots of good answers here. Here's what I do: I built a parameter class that takes care of the pairing syntax as well as url encoding.

    #import "NSString+URLEncoding.h"
    
    @interface UrlParameter ()
    
    @property (strong, nonatomic) NSString *pair;
    @property (strong, nonatomic) NSString *encodedPair;
    
    @end
    
    @implementation UrlParameter
    
    @synthesize pair = _pair;
    @synthesize encodedPair = _encodedPair;
    
    
    + (UrlParameter *)withName:(NSString *)name value:(NSString *)value {
    
        UrlParameter *answer = [[UrlParameter alloc] init];
        answer.pair = [NSString stringWithFormat:@"%@=%@", name, value];
        answer.encodedPair = [NSString stringWithFormat:@"%@=%@", [name urlEncode], [value urlEncode]];
    
        return answer;  // autorelease if non-ARC
    }
    
    @end
    

    This relies on this NSString category method that I cobbled out of a couple posts. (Also ARC-ready):

    @implementation NSString (URLEncoding)
    
    - (NSString *)urlEncodeUsingEncoding:(CFStringEncoding)encoding {
    
        return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                   (__bridge CFStringRef)self,
                                                                   NULL,
                                                                   CFSTR("!*'();:@&=+$,/?%#[]"),
                                                                   encoding));
    }
    
    - (NSString *)urlEncode {
        return [self urlEncodeUsingEncoding:kCFStringEncodingUTF8];
    }
    
    @end
    

    I also subclass the URL request and give it mutable array of parameters, then, before it starts, it does this:

    // in the interface
    @property (strong, nonatomic) NSMutableArray *parameters;
    
    - (void)prepareParameters {
    
        NSMutableString *encodedParameterPairs = [NSMutableString stringWithCapacity:256];
    
        int position = 1;
        for (UrlParameter *requestParameter in self.parameters) {
            [encodedParameterPairs appendString:[requestParameter encodedPair]];
            if (position < [self.parameters count]) [encodedParameterPairs appendString:@"&"];
            position++;
        }
    
        if ([[self HTTPMethod] isEqualToString:@"GET"] || [[self HTTPMethod] isEqualToString:@"DELETE"]) {
    
            NSString *urlString = [NSString stringWithFormat:@"%@?%@", [self URLString], encodedParameterPairs];
            [self setURL:[NSURL URLWithString:urlString]];
    
        } else {
    
            // POST, PUT
            NSData *postData = [encodedParameterPairs dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
            [self setHTTPBody:postData];
            [self setValue:[NSString stringWithFormat:@"%d", [postData length]] forHTTPHeaderField:@"Content-Length"];
            [self setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
    }