MPOAuthConnectionをPOSTとHTTPヘッダに対応させる

昨日の記事、『iPhoneTwitterクライアントでMPOAuthConnectionを使う』(id:nkmrshn:20090911)で、最後にMPOAuthConnectionをPOSTに対応させる方法、Google Code Archive - Long-term storage for Google Code Project Hosting.へのリンクを書きました。
これでもPOSTできるのですが、私はついでにHTTPヘッダを付加させてやりたいと思い、この方法は取らないことにしました。例えばTwitterのSearch APIでは、HTTPヘッダとしてUser-Agentが無いと、Rate limitingが通常より規制されるからです。

Search API usage requires that applications include a unique and identifying User Agent string. A HTTP Referrer is expected but is not required. Consumers using the Search API but failing to include a User Agent string will receive a lower rate limit.

また、MPOAuthAPIクラスのperformMethodメソッドを呼び出す際、「POSTする/しない」と「HTTPヘッダ」を指定したかったというのもあります。そこで、以下のようにMPOAuthAPI.h/mおよびMPOAuthURLRequest.h/mにコードを追加・修正しました。


MPOAuthAPI.h

- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction doPost:(BOOL)inPost withHeaders:(NSDictionary *)inHeaders;


MPOAuthAPI.m

- (void)performMethod:(NSString *)inMethod withTarget:(id)inTarget andAction:(SEL)inAction {
  [self performMethod:inMethod atURL:self.baseURL withParameters:nil withTarget:inTarget andAction:inAction doPost:NO withHeaders:nil];
}

- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction {
  [self performMethod:inMethod atURL:inURL withParameters:inParameters withTarget:inTarget andAction:inAction doPost:NO withHeaders:nil];
}

- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction doPost:(BOOL)inPost withHeaders:(NSDictionary *)inHeaders {
  if (!inMethod && ![inURL path] && ![inURL query]) {
    [NSException raise:@"MPOAuthNilMethodRequestException" format:@"Nil was passed as the method to be performed on %@", inURL];
  }
	
  NSURL *requestURL = inMethod ? [NSURL URLWithString:inMethod relativeToURL:inURL] : inURL;
  MPOAuthURLRequest *aRequest = [[MPOAuthURLRequest alloc] initWithURL:requestURL andParameters:inParameters doPost:inPost withHeaders:inHeaders];


MPOAuthURLRequest.h

@private
  NSURL                *_url;
  NSString             *_httpMethod;
  NSURLRequest         *_urlRequest;
  NSMutableArray       *_parameters;
  NSMutableDictionary  *_headers;
}

@property (nonatomic, readwrite, retain) NSURL *url;
@property (nonatomic, readwrite, retain) NSString *HTTPMethod;
@property (nonatomic, readonly, retain) NSURLRequest *urlRequest;
@property (nonatomic, readwrite, retain) NSMutableArray *parameters;
@property (nonatomic, readwrite, retain) NSMutableDictionary *headers;

- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)inParameters;

- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)inParameters doPost:(BOOL)inPost withHeaders:(NSDictionary *)inHeaders;


MPOAuthURLRequest.m

- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)inParameters {
  return [self initWithURL:inURL andParameters:inParameters doPost:NO withHeaders:nil];
}

- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)inParameters doPost:(BOOL)inPost withHeaders:(NSDictionary *)inHeaders {
  if (self = [super init]) {
    self.url = inURL;
    _parameters = inParameters ? [inParameters mutableCopy] : [[NSMutableArray alloc] initWithCapacity:10];
    _headers = inHeaders ? [inHeaders mutableCopy] : nil;
    self.HTTPMethod = inPost ? @"POST" : @"GET";
  }
  return self;
}

- (oneway void)dealloc {
  self.url = nil;
  self.HTTPMethod = nil;
  self.urlRequest = nil;
  self.parameters = nil;
  self.headers = nil;
	
  [super dealloc];
}

@synthesize url = _url;
@synthesize HTTPMethod = _httpMethod;
@synthesize urlRequest = _urlRequest;
@synthesize parameters = _parameters;
@synthesize headers = _headers;


MPOAuthURLRequest.m (urlRequestSignedWithSecretメソッド内)

    [aRequest setURL:self.url];
    [aRequest setValue:[NSString stringWithFormat:@"%d", [postData length]] forHTTPHeaderField:@"Content-Length"];
    [aRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [aRequest setHTTPBody:postData];
  }
	
  if(self.headers) {
    NSArray *keys = [self.headers allKeys];
    for(NSString *key in keys) {
      [aRequest setValue:[self.headers objectForKey:key] forHTTPHeaderField:key];
      MPLog(@"header - %@:%@", key, [aRequest valueForHTTPHeaderField:key]);
    }
  }
		
  [parameterString release];
  [signatureParameter release];


実際に呼び出す例として、TwitterのSearch APIを以下のように書いてテストしてみました。MPOAuthのサンプルアプリ、MPOAuthMobileのRootViewController.xibに「Search」などというUIButtonを配置し、タップするとRootViewController.mのsearchButtonPressedメソットを呼びよう事前にInterface Builderで設定してあります。

- (void)_searchPost:(NSURL *)inURL withResponseString:(NSString *)inString {
  textOutput.text = inString;
}

- (IBAction)searchButtonPressed:(id)sender {
  NSArray *parameters = [MPURLRequestParameter parametersFromString:@"q=iPhone"];
	
  NSString *userAgent = [NSString stringWithFormat:@"%@/%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]];
  NSDictionary *headers = [NSDictionary dictionaryWithObject:userAgent forKey:@"User-Agent"];
	
  NSURL *url = [NSURL URLWithString:@"http://search.twitter.com/search.atom"];

  [_oauthAPI performMethod:nil atURL:url withParameters:parameters withTarget:self andAction:@selector(_searchPost:withResponseString:) doPost:NO withHeaders:headers];
}

余談ですが、仕事ではMPOAuthConnectionを使っていません。理由は簡単で、MPOAuthConnectionの存在を昨日、偶然、知ったからです。