Ben Copsey

Refactor: move multipart form stuff into subclass

Added iphone example
//
// ASIFormDataRequest.h
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import "ASIHTTPRequest.h"
@interface ASIFormDataRequest : ASIHTTPRequest {
//Parameters that will be POSTed to the url
NSMutableDictionary *postData;
//Files that will be POSTed to the url
NSMutableDictionary *fileData;
}
#pragma mark setup request
//Add a POST variable to the request
- (void)setPostValue:(id)value forKey:(NSString *)key;
//Add the contents of a local file as a POST variable to the request
- (void)setFile:(NSString *)filePath forKey:(NSString *)key;
@end
... ...
//
// ASIFormDataRequest.m
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import "ASIFormDataRequest.h"
@implementation ASIFormDataRequest
#pragma mark init / dealloc
- (id)initWithURL:(NSURL *)newURL
{
self = [super initWithURL:newURL];
postData = nil;
fileData = nil;
return self;
}
- (void)dealloc
{
[postData release];
[fileData release];
[super dealloc];
}
#pragma mark setup request
- (void)setPostValue:(id)value forKey:(NSString *)key
{
if (!postData) {
postData = [[NSMutableDictionary alloc] init];
}
[postData setValue:value forKey:key];
}
- (void)setFile:(NSString *)filePath forKey:(NSString *)key
{
if (!fileData) {
fileData = [[NSMutableDictionary alloc] init];
}
[fileData setValue:filePath forKey:key];
}
#pragma mark request logic
// Create the request
- (void)main
{
// If the user didn't specify post data, we will let ASIHTTPRequest use the value of postBody
if ([postData count] == 0 && [fileData count] == 0) {
[super main];
return;
}
//Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
NSString *stringBoundary = @"0xKhTmLbOuNdArY";
if ([fileData count] > 0) {
//We need to use multipart/form-data when using file upload
[self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary]];
}
//Since we've got post data, let's set the post body to an empty NSMutableData object
[self setPostBody:[NSMutableData data]];
[postBody appendData:[[NSString stringWithFormat:@"--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
//Adds post data
NSData *endItemBoundary = [[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding];
NSEnumerator *e = [postData keyEnumerator];
NSString *key;
int i=0;
while (key = [e nextObject]) {
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[postData objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]];
i++;
if (i != [postData count] || [fileData count] > 0) { //Only add the boundary if this is not the last item in the post body
[postBody appendData:endItemBoundary];
}
}
//Adds files to upload
NSData *contentTypeHeader = [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding];
e = [fileData keyEnumerator];
i=0;
while (key = [e nextObject]) {
NSString *filePath = [fileData objectForKey:key];
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",key,[filePath lastPathComponent]] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:contentTypeHeader];
[postBody appendData:[NSData dataWithContentsOfMappedFile:filePath]];
i++;
if (i != [fileData count]) { //Only add the boundary if this is not the last item in the post body
[postBody appendData:endItemBoundary];
}
}
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
//Now we've created our post data, construct the request
[super main];
}
@end
... ...
... ... @@ -10,11 +10,9 @@
// Portions are based on the ImageClient example from Apple:
// See: http://developer.apple.com/samplecode/ImageClient/listing37.html
#import <Cocoa/Cocoa.h>
#import "ASIProgressDelegate.h"
@interface ASIHTTPRequest : NSOperation {
//The url for this operation, should include GET params in the query string where appropriate
... ... @@ -23,11 +21,11 @@
//The delegate, you need to manage setting and talking to your delegate in your subclasses
id delegate;
//Parameters that will be POSTed to the url
NSMutableDictionary *postData;
//HTTP method to use (GET / POST / PUT / DELETE). Defaults to GET
NSString *requestMethod;
//Files that will be POSTed to the url
NSMutableDictionary *fileData;
//Request body
NSMutableData *postBody;
//Dictionary for custom HTTP request headers
NSMutableDictionary *requestHeaders;
... ... @@ -138,11 +136,6 @@
//Add a custom header to the request
- (void)addRequestHeader:(NSString *)header value:(NSString *)value;
//Add a POST variable to the request
- (void)setPostValue:(id)value forKey:(NSString *)key;
//Add the contents of a local file as a POST variable to the request
- (void)setFile:(NSString *)filePath forKey:(NSString *)key;
#pragma mark get information about this request
... ... @@ -259,4 +252,6 @@
@property (retain) NSMutableData *receivedData;
@property (retain) NSDate *lastActivityTime;
@property (assign) NSTimeInterval timeOutSeconds;
@property (retain) NSString *requestMethod;
@property (retain) NSMutableData *postBody;
@end
... ...
... ... @@ -38,10 +38,9 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
- (id)initWithURL:(NSURL *)newURL
{
[super init];
self = [super init];
[self setRequestMethod:@"GET"];
lastBytesSent = 0;
postData = nil;
fileData = nil;
username = nil;
password = nil;
requestHeaders = nil;
... ... @@ -72,19 +71,24 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
CFRelease(request);
}
[self cancelLoad];
[postBody release];
[requestCredentials release];
[error release];
[postData release];
[fileData release];
[requestHeaders release];
[requestCookies release];
[downloadDestinationPath release];
[outputStream release];
[username release];
[password release];
[domain release];
[authenticationRealm release];
[url release];
[authenticationLock release];
[lastActivityTime release];
[responseCookies release];
[receivedData release];
[responseHeaders release];
[requestMethod release];
[super dealloc];
}
... ... @@ -100,23 +104,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
}
- (void)setPostValue:(id)value forKey:(NSString *)key
{
if (!postData) {
postData = [[NSMutableDictionary alloc] init];
}
[postData setValue:value forKey:key];
}
- (void)setFile:(NSString *)filePath forKey:(NSString *)key
{
if (!fileData) {
fileData = [[NSMutableDictionary alloc] init];
}
[fileData setValue:filePath forKey:key];
}
#pragma mark get information about this request
- (BOOL)isFinished
... ... @@ -145,15 +132,9 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
- (void)main
{
complete = NO;
// We'll make a post request only if the user specified post data
NSString *method = @"GET";
if ([postData count] > 0 || [fileData count] > 0) {
method = @"POST";
}
// Create a new HTTP request.
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)method, (CFURLRef)url, kCFHTTPVersion1_1);
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)requestMethod, (CFURLRef)url, kCFHTTPVersion1_1);
if (!request) {
[self failWithProblem:[NSString stringWithFormat:@"Unable to create request for: %@",url]];
return;
... ... @@ -166,9 +147,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
[ASIHTTPRequest setSessionCredentials:nil];
}
}
//Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
NSString *stringBoundary = @"0xKhTmLbOuNdArY";
//Add cookies from the persistant (mac os global) store
if (useCookiePersistance) {
... ... @@ -200,48 +178,11 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
for (header in requestHeaders) {
CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]);
}
CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Content-Type", (CFStringRef)[NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary]);
if ([postData count] > 0 || [fileData count] > 0) {
NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[[NSString stringWithFormat:@"--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
//Adds post data
NSData *endItemBoundary = [[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding];
NSEnumerator *e = [postData keyEnumerator];
NSString *key;
int i=0;
while (key = [e nextObject]) {
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[postData objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]];
i++;
if (i != [postData count] || [fileData count] > 0) { //Only add the boundary if this is not the last item in the post body
[postBody appendData:endItemBoundary];
}
}
//Adds files to upload
NSData *contentTypeHeader = [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding];
e = [fileData keyEnumerator];
i=0;
while (key = [e nextObject]) {
NSString *filePath = [fileData objectForKey:key];
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",key,[filePath lastPathComponent]] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:contentTypeHeader];
[postBody appendData:[NSData dataWithContentsOfMappedFile:filePath]];
i++;
if (i != [fileData count]) { //Only add the boundary if this is not the last item in the post body
[postBody appendData:endItemBoundary];
}
}
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Set the body.
//If this is a post request and we have data to send, add it to the request
if ([self postBody]) {
CFHTTPMessageSetBody(request, (CFDataRef)postBody);
postLength = [postBody length];
}
... ... @@ -887,4 +828,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
@synthesize receivedData;
@synthesize lastActivityTime;
@synthesize timeOutSeconds;
@synthesize requestMethod;
@synthesize postBody;
@end
... ...
... ... @@ -5,7 +5,6 @@
// Copyright 2008 All-Seeing Interactive. All rights reserved
//
#import <Cocoa/Cocoa.h>
@protocol ASIProgressDelegate
... ...
... ... @@ -5,7 +5,6 @@
// Copyright 2008 All-Seeing Interactive Ltd. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class ASIHTTPRequest;
@interface AppDelegate : NSObject {
... ...
... ... @@ -7,6 +7,7 @@
#import "AppDelegate.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
@implementation AppDelegate
... ... @@ -26,7 +27,7 @@
- (IBAction)simpleURLFetch:(id)sender
{
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://asi/"]] autorelease];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/"]] autorelease];
//Customise our user agent, for no real reason
[request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"];
... ... @@ -165,7 +166,7 @@
[networkQueue cancelAllOperations];
[progressIndicator setDoubleValue:0];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ignore"]] autorelease];
ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ignore"]] autorelease];
[request setDelegate:self];
[request setUploadProgressDelegate:progressIndicator];
[request setPostValue:@"test" forKey:@"value1"];
... ...
//
// FirstViewController.h
// tabtest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright All-Seeing Interactive 2008. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FirstViewController : UIViewController {
}
@end
... ...
//
// FirstViewController.m
// tabtest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright All-Seeing Interactive 2008. All rights reserved.
//
#import "FirstViewController.h"
@implementation FirstViewController
/*
// Override initWithNibName:bundle: to load the view using a nib file then perform additional customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically.
- (void)loadView {
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
- (void)dealloc {
[super dealloc];
}
@end
... ...
//
// main.m
// Mac-Os-Sample-main.m
// asi-http-request
//
// Created by Ben Copsey on 09/07/2008.
... ...
... ... @@ -6,7 +6,6 @@
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface NSHTTPCookie (ValueEncodingAdditions)
... ...
//
// QueueViewController.h
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface QueueViewController : UIViewController {
NSOperationQueue *networkQueue;
IBOutlet UIImageView *imageView1;
IBOutlet UIImageView *imageView2;
IBOutlet UIImageView *imageView3;
IBOutlet UIProgressView *progressIndicator;
}
- (IBAction)fetchThreeImages:(id)sender;
@end
... ...
//
// QueueViewController.m
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import "QueueViewController.h"
#import "ASIHTTPRequest.h"
@implementation QueueViewController
- (void)awakeFromNib
{
networkQueue = [[NSOperationQueue alloc] init];
}
- (IBAction)fetchThreeImages:(id)sender
{
[imageView1 setImage:nil];
[imageView2 setImage:nil];
[imageView3 setImage:nil];
[networkQueue cancelAllOperations];
[progressIndicator setProgress:0];
ASIHTTPRequest *request;
request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/logo.png"]] autorelease];
[request setDelegate:self];
[request setDidFinishSelector:@selector(imageFetchComplete:)];
[networkQueue addOperation:request];
request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/trailsnetwork.png"]] autorelease];
[request setDelegate:self];
[request setDidFinishSelector:@selector(imageFetchComplete:)];
[networkQueue addOperation:request];
request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/sharedspace20.png"]] autorelease];
[request setDelegate:self];
[request setDidFinishSelector:@selector(imageFetchComplete:)];
[networkQueue addOperation:request];
}
- (void)imageFetchComplete:(ASIHTTPRequest *)request
{
UIImage *img = [UIImage imageWithData:[request receivedData]];
if (img) {
if ([imageView1 image]) {
if ([imageView2 image]) {
[imageView3 setImage:img];
} else {
[imageView2 setImage:img];
}
} else {
[imageView1 setImage:img];
}
}
[progressIndicator setProgress:[progressIndicator progress]+0.3333];
}
- (void)dealloc {
[networkQueue release];
[super dealloc];
}
@end
... ...
To do:
Rest unit tests (get / post / put /delete)
Changes for release notes:
* Set request method (get,post,delete,put)
* Fixed a few memory leaks (thanks clang static analyser!)
* Split ASIHTTPRequest in two
[NEW!] Documentation is available at: http://allseeing-i.com/asi-http-request
ASIHTTPRequest is an easy to use wrapper around the CFNetwork API that makes some of the more tedious aspects of communicating with web servers easier.
... ...
//
// SynchronousViewController.h
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface SynchronousViewController : UIViewController {
IBOutlet UITextView *htmlSource;
}
- (IBAction)simpleURLFetch:(id)sender;
@end
... ...
//
// SynchronousViewController.m
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import "SynchronousViewController.h"
#import "ASIHTTPRequest.h"
@implementation SynchronousViewController
- (IBAction)simpleURLFetch:(id)sender
{
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/"]] autorelease];
//Customise our user agent, for no real reason
[request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"];
[request start];
if ([request dataString]) {
[htmlSource setText:[request dataString]];
}
}
@end
... ...
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff was suppressed by a .gitattributes entry.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainWindow</string>
</dict>
</plist>
... ...
//
// Prefix header for all source files of the 'asi-http-request' target in the 'asi-http-request' project
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>
#import <UIKit/UIKit.h>
#endif
... ...
//
// iPhone-Sample-main.m
// asi-http-request
//
// Created by Ben Copsey on 09/07/2008.
// Copyright __MyCompanyName__ 2008. All rights reserved.
//
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
... ...
//
// iPhoneSampleAppDelegate.h
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright All-Seeing Interactive 2008. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface iPhoneSampleAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
@end
... ...
//
// iPhoneSampleAppDelegate.m
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright All-Seeing Interactive 2008. All rights reserved.
//
#import "iPhoneSampleAppDelegate.h"
#import "ASIHTTPRequest.h"
@implementation iPhoneSampleAppDelegate
@synthesize window;
@synthesize tabBarController;
- (void)dealloc {
[tabBarController release];
[window release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}
@end
... ...
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.