Playing with HTTP libraries

It’s fun to find out how to tackle the same task in different programming languages; in this case, it’s all about doing HTTP requests over a network: fortunately, there are networking libraries in virtually all major programming languages. In my current project, I’m generating wrappers easing the access to the core of the project itself, a RESTful API. This way, developers interested in using the API can just take a wrapper, include it in their projects, and start coding right away. No need to know this (relatively low-level) stuff; just use the API. The wrappers themselves are auto-generated from the API definition itself, but that’s another story ;)

Below there is a sample of the different ways I’ve found to do a network access to a remote server, using HTTP Basic Authentication and a couple of headers, in PHP, Ruby, Python, JavaScript, and even Objective-C! I’m even generating ActionScript 3.0 code, but I’m not a Flash coder :) So I’ll post the wrappers that work best at the moment, and in the future I’ll include other examples, particularly for .NET, C++ and Java.

In all the cases below, there is a “request” function or method that takes an HTTP verb (GET, POST, PUT, DELETE, etc), a URL (without the slash “/” at the beginning) and some parameter data, in the form of a dictionary. The function wraps the underlying libraries of each programming language, offering a simpler interface, and allowing for HTTP Basic Authentication (for HTTP Digest Authentication it would be much, much more complex!). There are synchronous (useful for server or command-line applications) and asynchronous versions (for GUI systems). Off to the code!

In PHP (synchronous):

$conf = array(
  "server" => "localhost",
  "username" => "",
  "password" => ""
);
function request($verb, $url, $parameters = array()) {
  global $conf;
  $headers = array(
    "Content-Type: text/html; charset=utf-8",
    "Accept: application/javascript",
    "Cache-Control: no-cache",
    "Pragma: no-cache",
    "Content-length: " . strlen($xml_data),
    "Authorization: Basic " . base64_encode($conf["username"] . ":" . $conf["password"])
  );
  $path = $conf["server"] . "/" . $url;
  $conn = curl_init();
  curl_setopt($conn, CURLOPT_URL, $path);
  curl_setopt($conn, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($conn, CURLOPT_USERAGENT, "Identify yourself!");
  curl_setopt($conn, CURLOPT_CUSTOMREQUEST, strtoupper($verb));
  curl_setopt($conn, CURLOPT_POSTFIELDS, $xml_data);
  curl_setopt($conn, CURLOPT_RETURNTRANSFER, 1);
  $data = curl_exec($conn);
  $code = curl_getinfo($conn, CURLINFO_HTTP_CODE);
  if($code == 200 && strtoupper($verb) == "GET") {
    return json_decode($data);
  }
  else {
    return $data;
  }
  curl_close($conn);
  return $data;
}

The curl library has a distinctive C smell :) The good thing is that after doing this wrapper, doing the C++ one will be fairly straightforward!

In Ruby (synchronous):

require 'net/http'
require 'uri'
require 'json'
$conf = {
    "server" => "localhost",
    "username" => "",
    "password" => ""
}
def request(verb, url, parameters = nil)
  Net::HTTP.start($conf["server"]) do |http|
    headers = {
        "Accept" => "application/javascript",
        "User-Agent" => "Identify yourself!"
    }
    path = "/" + url
    req = nil
    case verb.upcase
      when "GET":
        req = Net::HTTP::Get.new(path, headers)
      when "POST":
        req = Net::HTTP::Post.new(path, headers)
      when "PUT":
        req = Net::HTTP::Put.new(path, headers)
      when "DELETE":
        req = Net::HTTP::Delete.new(path, headers)
      else
        raise Exception.new("Invalid HTTP verb")
    end
    req.basic_auth $conf["username"], $conf["password"]
    req.set_form_data(parameters) if not parameters == nil
    response = http.request(req)
    if response.code == "200" && verb.upcase == "GET"
        JSON.parse(response.body)
    else
        print response.code + " " + response.message + " " + response.body
    end
  end
end

The Ruby library is the only one I found so far that features the four HTTP verbs in the interface!

In Python (synchronous):

import simplejson
import httplib, urllib
import base64
conf = {
    "server": "localhost",
    "username": "",
    "password": "",
}
def request(verb, url, parameters = {}):
    params = urllib.urlencode(parameters)
    base64string = base64.encodestring('%s:%s' % (conf["username"], conf["password"]))[:-1]
    headers = {
        "Accept": "application/javascript",
        "Authorization": "Basic %s" % base64string,
        "User-Agent": "Identify yourself!",
    }
    conn = httplib.HTTPConnection(conf["server"])
    conn.request(verb.upper(), "/" + url, params, headers)
    response = conn.getresponse()
    if response.status == 200 and verb.upper() == "GET":
        obj = simplejson.loads(response.read())
        return obj
    else:
        print response.status, response.reason, response.read()
    conn.close()

I much prefer the Ruby way, if you ask me. I don’t know, it’s more elegant. I can’t get used to the indenting!

In JavaScript, using Prototype (asynchronous):

var conf = {
    server: "localhost",
    username: "",
    password: ""
};
var request = function(verb, url, parameters, successHandler, errorHandler) {
    // A really ugly way to do HTTP Basic Auth, I know :)
    var api_url = ["http://", conf.username.replace(/@/, "%40"), ":", conf.password, "@", conf.server, "/", url].join("");
    new Ajax.Request(api_url, {
        method: verb.toLowerCase(),
        parameters: parameters,
        requestHeaders: {
            "Accept": "application/javascript",
            "Cache-Control": "no-cache",
            "Pragma": "no-cache",
            "Content-Type": "text/html; charset=utf-8"
        },
        onSuccess: function(transport) {
            if(successHandler) successHandler(transport);
        },
        onFailure: function(transport) {
            if(errorHandler) errorHandler(transport);
        }
    });
};

Again in JavaScript, but this time using jQuery (asynchronous):

var conf = {
    server: "localhost",
    username: "",
    password: ""
};
var request = function(verb, url, parameters, successHandler, errorHandler) {
    var api_url = ["http://", conf.username.replace(/@/, "%40"), ":", conf.password, "@", conf.server,"/", url].join("");
    $.ajax({
        type: verb.toUpperCase(),
        url: api_url,
        async: true,
        data: parameters,
        cache: false,
        // jQuery does not allow to change headers otherwise (?) than using the beforeSend() hook:
        beforeSend: function(xhr) {
            xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8");
            xhr.setRequestHeader('Accept', 'application/javascript');
            xhr.setRequestHeader('Cache-Control', 'no-cache');
            xhr.setRequestHeader('Pragma', 'no-cache');
        },
        complete: function(engine, textStatus) {
            if (engine.readyState == 4) {
                if (engine.status == 200 || engine.status == 201) { if(successHandler) successHandler(engine); }
                else { if(errorHandler) errorHandler(engine); }
            }
        }
    });
};

The last JavaScript one, but this time using bare bones XMLHttpRequest (asynchronous):

var conf = {
    server: "localhost",
    username: "",
    password: ""
};
var request = function(verb, url, parameters, successHandler, errorHandler) {
    var api_url = ["http://", conf.username.replace(/@/, "%40"), ":", conf.password, "@", conf.server,"/", url].join("");
    var params = [];
    for(var item in parameters) {
        params.push(escape(item) + "=" + escape(parameters[item]));
    }
    var engine = null;
    if (window.XMLHttpRequest) {
        engine = new XMLHttpRequest();
    }
    if (window.ActiveXObject) {
        engine = new ActiveXObject("Microsoft.XMLHTTP");
    }
    engine.onreadystatechange = function stateChange() {
        if (engine.readyState == 4) {
            if (engine.status == 200 || engine.status == 201) { if(successHandler) successHandler(engine); }
            else { if(errorHandler) errorHandler(engine); }
        }
    };
    engine.open(verb.toUpperCase(), api_url, true);
    // setRequestHeader() must be called AFTER open() !!
    engine.setRequestHeader("Content-Type", "text/html; charset=utf-8");
    engine.setRequestHeader('Accept', 'application/javascript');
    engine.setRequestHeader('Cache-Control', 'no-cache');
    engine.setRequestHeader('Pragma', 'no-cache');
    engine.send(params.join("&"));
};

To use these JavaScript versions, just pass a couple of functions as parameters, and you’re done; they will be called in case of success or error, asynchronously.

Finally, the Cocoa / Objective-C wrapper (both synchronous and asynchronous, header and implementation files):

/*
Command-line example that uses this library:

import

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool;
    pool = [[NSAutoreleasePool alloc] init];
    AKResourceSubClass* resource;
    resource = [[AKResourceSubClass alloc] init];
    [resource get];
    NSDictionary* plist = [resource getPropertyList];
    // Do something with the results...
    [pool drain];
    return 0;
}
*/

import

@interface AKResource : NSObject {
    NSMutableData* receivedData;
    NSString* mimeType;
    NSString* server;
    NSString* username;
    NSString* password;
    BOOL async;
}
-(id)init;
-(void)setServer:(NSString*)serverName;
-(void)setUsername:(NSString*)user
    andPassword:(NSString*)pwd;
-(void)connection:(NSURLConnection *)connection
    didReceiveResponse:(NSURLResponse *)response;
-(void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data;
-(void)connection:(NSURLConnection *)connection
    didFailWithError:(NSError *)error;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
-(void)sendRequestTo:(NSString*)urlString
    usingVerb:(NSString*)verb
        withParameters:(NSDictionary*)parameters;
-(void)setAsynchronous:(BOOL)asynchronously;
-(NSDictionary*)getPropertyList;
-(NSString*)getResponseText;
@end

Now the implementation file:

import “AKResource.h”

@implementation AKResource
-(id)init
{
    self = [super init];
    if(self)
    {
        receivedData = [[NSMutableData alloc] init];
        mimeType = @"application/plist";
        server = @"localhost";
        username = @"";
        password = @"";
        async = NO;
    }
    return self;
}
-(void)setServer:(NSString*)serverName
{
    server = serverName;
}
-(void)setUsername:(NSString*)user andPassword:(NSString*)pwd
{
    username = user;
    password = pwd;
}
-(void)sendRequestTo:(NSString*)resource
    usingVerb:(NSString*)verb
    withParameters:(NSDictionary*)parameters
{
    NSURL* url = [NSURL URLWithString:[NSString
        stringWithFormat:@"http://%@/%@", server, resource]];
    NSMutableDictionary* headers = [[NSMutableDictionary alloc] init];
    [headers setValue:@"text/html; charset=utf-8"
        forKey:@"Content-Type"];
    [headers setValue:mimeType forKey:@"Accept"];
    [headers setValue:@"no-cache" forKey:@"Cache-Control"];
    [headers setValue:@"no-cache" forKey:@"Pragma"];
    NSMutableURLRequest* request;
    request = [NSMutableURLRequest requestWithURL:url
                cachePolicy:NSURLRequestUseProtocolCachePolicy
                timeoutInterval:60.0];
    [request setHTTPMethod:verb];
    [request setAllHTTPHeaderFields:headers];
    if (parameters)
    {
        NSMutableString* params = [[NSMutableString alloc] init];
        for (id key in parameters)
        {
            [params appendFormat:@"%@=%@&",
                [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
                [[parameters objectForKey:key]
                    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        }
        [params deleteCharactersInRange:NSMakeRange([params length] - 1, 1)];
        NSData* body = [params dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody:body];
    }
    if (async)
    {
        NSURLConnection* connection;
        connection = [[NSURLConnection alloc]  initWithRequest:request
                                                delegate:self
                                                startImmediately:YES];
        if (!connection)
        {
            NSLog(@"Could not open connection to resource");
        }
    }
    else
    {
        NSURLResponse* response = [[NSURLResponse alloc] init];
        NSError* error = [[NSError alloc] init];
        NSData* data = [NSURLConnection
            sendSynchronousRequest:request
            returningResponse:&response
            error:&error];
        [receivedData setData:data];
    }
}
-(void)connection:(NSURLConnection *)connection
        didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLCredential *newCredential;
    newCredential = [NSURLCredential credentialWithUser:username
                        password:password
                        persistence:NSURLCredentialPersistenceNone];
    [[challenge sender] useCredential:newCredential
        forAuthenticationChallenge:challenge];
}
-(void)connection:(NSURLConnection *)connection
    didReceiveResponse:(NSURLResponse *)response
{
    NSHTTPURLResponse* httpResponse;
    httpResponse = (NSHTTPURLResponse*)response;
    int statusCode = [httpResponse statusCode];
    if (statusCode != 200)
    {
        NSMutableDictionary* info;
        info = [NSMutableDictionary dictionaryWithObject:[response URL]
            forKey:NSErrorFailingURLStringKey];
        [info setObject:@"An error code different than 200 was received!"
            forKey:NSLocalizedDescriptionKey];
        NSError* error = [NSError errorWithDomain:@"API Wrapper"
            code:statusCode
            userInfo:info];
        NSDictionary* errorData = [NSDictionary
            dictionaryWithObject:error
            forKey:@"error"];
        [[NSNotificationCenter defaultCenter]
            postNotificationName:@"ConnectionDidFailWithStatusCodeNotOK"
            object:self
            userInfo:errorData];
    }
    [receivedData setLength:0];
}
-(void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data
{
    [receivedData appendData:data];
}
-(void)connection:(NSURLConnection *)connection
    didFailWithError:(NSError *)error
{
    [connection release];
    NSDictionary* errorData = [NSDictionary
                                dictionaryWithObject:error
                                forKey:@"error"];
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"ConnectionDidFailWithError"
        object:self
        userInfo:errorData];
    NSLog(@"Connection failed! Error - %@ %@",
          [error localizedDescription],
          [[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"ConnectionDidFinishLoading"
        object:self];
    [connection release];
}
-(void)setAsynchronous:(BOOL)asynchronously
{
    async = asynchronously;
}
-(NSDictionary*)getPropertyList
{
    NSString* errorStr = nil;
    NSDictionary* propertyList;
    NSPropertyListFormat format;
    propertyList = [NSPropertyListSerialization
                    propertyListFromData:receivedData
                    mutabilityOption: NSPropertyListImmutable
                    format: &format
                    errorDescription: &errorStr];
    return propertyList;
}
-(NSString*)getResponseText
{
    return [[NSString alloc]
            initWithData:receivedData
            encoding:NSUTF8StringEncoding];
}
@end

Rather verbose this last one huh? Objective-C has a distinctive characteristic, inherited from Smalltalk (I suppose): methods with named parameters, like stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding (decidedly, one of the longest method calls ever :) The good thing is that the code reads like a book. This is why I like this language, as well as Ruby: you can read code in these languages very easily.

Another nice aspect of this Objective-C wrapper is that if you have an API that can return “Property Lists” (like the one I’ve been working on lately), you can send object graphs serialized in XML that are completely Cocoa-compatible. That’s what the getPropertyList method does: client code can deal with standard NSDictionary instances, no need to do anything else!

Of course these are not the only ways to do this, but so far these wrappers have helped me a lot. Don’t hesitate to leave your comments below! And most important, have fun! As always, use this code at your own risk.