How to make an HTTP POST request in many languages

To make StatHat accessible from as many languages and platforms as possible, we needed to figure out how to make an HTTP POST from a lot of different langauges. We tried to only use system libraries so that there would be no external dependencies, but this wasn’t always possible.

Here is the result of our work, in alphabetical order:

#####C# (C sharp, .NET) To make a POST request with just the system libraries required a bit of code for C#.

{% highlight csharp %} using System; using System.Net; using System.IO; using System.Text; using System.Collections.Generic;

namespace HTTPUtil { public class FormPoster { // Members HttpWebRequest Request; Dictionary Parameters; ReplyDelegate Reply; string RelUrl; string BaseUrl;

        // Methods
        public FormPoster(string base_url, string rel_url, Dictionary<string, string> parameters, ReplyDelegate replyDelegate)
        {
            this.BaseUrl = base_url;
            this.Parameters = parameters;
            this.Reply = replyDelegate;
            this.RelUrl = rel_url;
            this.PostForm();
        }
        public FormPoster(string base_url, string rel_url, Dictionary<string, string> parameters)
        {
            this.BaseUrl = base_url;
            this.Parameters = parameters;
            this.Reply = new ReplyDelegate((rep) => { });
            this.RelUrl = rel_url;
            this.PostForm();
        }

        private void PostForm()
        {
            this.Request = (HttpWebRequest)WebRequest.Create(this.BaseUrl + this.RelUrl);
            Request.Method = "POST";
            Request.ContentType = "application/x-www-form-urlencoded";
            Request.BeginGetRequestStream(this.RequestCallback, Request);
        }
        private void RequestCallback(IAsyncResult asyncResult)
        {

            string postData = "";
            foreach (string key in this.Parameters.Keys)
            {
                postData += encodeUriComponent(key) + "=" + encodeUriComponent(this.Parameters[key]) + "&";
            }
            Stream newStream = Request.EndGetRequestStream(asyncResult);
            StreamWriter streamWriter = new StreamWriter(newStream);
            streamWriter.Write(postData);
            streamWriter.Close();
            this.Request.BeginGetResponse(this.ResponseCallback, this.Request);
        }

        private string encodeUriComponent(string s)
        {
            string res = s.Replace("&", "%26");
            res = res.Replace(" ","%20");
            return res;
        }

        private void ResponseCallback(IAsyncResult asyncResult)
        {
            try
            {
                WebResponse response = this.Request.EndGetResponse(asyncResult);
                Stream dataStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(dataStream);
                string result = reader.ReadToEnd();
                this.Reply(result);
            }
            catch (Exception e)
            {
                this.Reply(e.Message);
            }
            finally { }
        }
    }
}

} {% endhighlight %}

#####curl

Not really a language, but useful to know nevertheless:

$ curl -d "stat=load avg&email=anything@stathat.com&value=0.92" http://api.stathat.com/ez

The -d specifies the post data.

#####go (golang)

The Go http package is excellent. Here’s how to do a POST:

{% highlight go %} import ( “http” “io/ioutil” “url” )

func postExample() { values := make(url.Values) values.Set(“email”, “anything@stathat.com”) values.Set(“stat”, “messages sent - female to male”) values.Set(“count”, “1”) r, err := http.PostForm(“http://api.stathat.com/ez", values) if err != nil { log.Printf(“error posting stat to stathat: %s”, err) return } body, _ := ioutil.ReadAll(r.Body) r.Body.Close() log.Printf(“stathat post result body: %s”, body) } {% endhighlight %}

#####iOS (Objective C)

To do an http post for iPhone or iPad applications without requiring the user to install any external libraries (such as the excellent ASIHTTPRequest) required a bit of code.

Header file:

#import <Foundation/Foundation.h>

@interface HTTPUtil : NSObject {
        NSURLConnection* _connection;
        NSMutableString* _body;
}

- (id)init;
- (void)httpPostDict:(NSDictionary*)params toPath:(NSString*)path;

Implementation file:

@interface HTTPUtil (Private)
- (void)releaseConnection;
@end

@implementation HTTPUtil

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

        _body = [[NSMutableString alloc] init];

        return self;
}

- (void)httpPostDict:(NSDictionary*)params toPath:(NSString*)path
{
        NSString* url = [NSString stringWithFormat:@"http://api.stathat.com/%@", path];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];

        NSMutableArray* dataPairs = [NSMutableArray array];
        for (NSString* key in [params allKeys]) {
                NSString* pair = [NSString stringWithFormat:@"%@=%@", key, [params objectForKey:key]];
                [dataPairs addObject:pair];
        }

        NSString* dataStr = [dataPairs componentsJoinedByString:@"&"];

        [request setHTTPMethod:@"POST"];
        [request setHTTPBody:[dataStr dataUsingEncoding:NSUTF8StringEncoding]];
        _connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain];
}

//
// NSURLConnection delegate methods
//

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
        if (connection != _connection) {
                return;
        }

        [_body appendString:[NSString stringWithUTF8String:[data bytes]]];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
        if (connection != _connection) {
                return;
        }

        [self releaseConnection];

        // do something with the error...
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
        if (connection != _connection) {
                return;
        }

        [self releaseConnection];

        // post completed successfully, response body is in _body
}

- (void)dealloc
{
        [_connection release];
        [_body release];
        [super dealloc];
}

@end

//
// Private methods
//
@implementation HTTPUtil (Private)

- (void)releaseConnection
{
        [_connection release];
        _connection = nil;
}

@end

#####Java

{% highlight java %} import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder;

void httpPost(String path, String data) { try { URL url = new URL(path); URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(data); wr.flush();

            BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = rd.readLine()) != null) {
                    System.out.println(line);
            }
            wr.close();
            rd.close();
    }
    catch (Exception e) {
            System.err.println(e);
    }

} {% endhighlight %}

Example usage:

{% highlight java %} String data = URLEncoder.encode(“email”, “UTF-8”) + “=” + URLEncoder.encode(email, “UTF-8”); data += “&” + URLEncoder.encode(“stat”, “UTF-8”) + “=” + URLEncoder.encode(statName, “UTF-8”); data += “&” + URLEncoder.encode(“value”, “UTF-8”) + “=” + URLEncoder.encode(value.toString(), “UTF-8”); httpPost(“http://api.stathat.com/ez", data); {% endhighlight %}

#####Lisp We settled on using the Drakma http client library for lisp. You can download it and learn how to install it here.

To make a post, do this:

(defun http-post (url parameters)
  (http-request url
                :method :post
                :parameters parameters))

Example usage:

(http-post "http://api.stathat.com/ez" '(("email" . email)
                                         ("stat" . stat)
                                         ("value" . value)))

#####Lua

For lua, you need to download the socket.http package.

{% highlight lua %} local http = require(“socket.http”)

http.request(“http://api.stathat.com/ez", “email=” .. email .. “&stat=” .. stat_name .. “&count=” .. count) {% endhighlight %}

While this might look like a GET request, it is actually a POST request since it includes the optional body parameter to the request function.

#####Node

{% highlight js %} var http = require(‘http’);

var HTTPUtil = { postRequest: function(host, path, params, callback) { var options = { host: host, port: 80, path: path, method: ‘POST’ };

            var request = http.request(options, function(res) {
                    res.on('data', function(chunk) {
                            callback(res.statusCode, chunk);
                    });
            });

            var key;
            for (key in params) {
                    request.write(key + "=" + params[key] + "\n");
            }
            request.end();
    },

}; {% endhighlight %}

#####Perl

{% highlight perl %} use HTTP::Request::Common qw(POST); use LWP::UserAgent;

sub httppost { my ($host, $path, $params) = @; my $ua = LWP::UserAgent->new; my $req = POST ‘http://’ . $host . ‘/’ . $path, $params; return $ua->request($req)->as_string; }; {% endhighlight %}

Example usage:

{% highlight perl %} http_post(‘api.stathat.com’, ‘ez’, [email => ‘someone@stathat.com’, stat => ‘message sent - female to male’, count => 1]) {% endhighlight %}

#####PHP

In PHP, it’s easiest to write your own function to make a POST request.

{% highlight php %} function do_post_request($url, $data, $optional_headers = null) { $params = array(‘http’ => array( ‘method’ => ‘POST’, ‘content’ => $data )); if ($optional_headers !== null) { $params[‘http’][‘header’] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, ‘rb’, false, $ctx); if (!$fp) { throw new Exception(“Problem with $url, $php_errormsg”); } $response = @stream_get_contents($fp); if ($response === false) { throw new Exception(“Problem reading data from $url, $php_errormsg”); } return $response; } {% endhighlight %}

When you call it, the $data parameter needs to have the post parameters encoded in it.

{% highlight php %} do_post_request(“http://api.stathat.com/ez", “email=$account_email&stat=$stat_name&count=$count”); {% endhighlight %}

#####Python

In Python, you need to encode the post parameters first.

{% highlight python %} import urllib import urllib2

pdata = urllib.urlencode({‘stat’: stat_name, ‘email’: account_email, ‘count’: count}) req = urllib2.Request(‘, pdata) resp = urllib2.urlopen(req)

print resp.read() {% endhighlight %}

#####Ruby

HTTP is pretty easy in Ruby. The Net::HTTP library handles it all for you.

{% highlight ruby %} require ‘net/http’ require ‘uri’

args = { :stat => stat_name, :email => account_email, :count => count } resp = Net::HTTP.post_form(URI.parse(“http://api.stathat.com/ez"), args) {% endhighlight %}

#####wget

Like curl, this isn’t a language, but also useful to know:

wget --post-data "stat=load avg&email=anything@stathat.com&value=0.92" http://api.stathat.com/ez

The --post-data argument should be self-explanatory.

#####Any others?

If anyone has any examples in other languages, please post in the comments if short enough or feel free to send us an email. We’ll update this document with any additions we get.


Comments? Send us a tweet.

Permalink:

Previous:
12 Stats You Could Track With StatHat
Next:
How To Track ActiveRecord Query Durations in Rails