Integrating HTTP/JSON Services

Stunt was designed with the goal of integrating external HTTP-based JSON servces in mind.

Stunt comes with built-in support for reading and parsing HTTP requests/responses, generating and parsing JSON documents, and executing external commands.  When combined, these tools provide almost everything you need to integrate witih services like Facebook's Graph API.

Direct Integration

It's relatively easy to throw together a verb in MOOcode that opens a connection to a foreign website, sends an HTTP request (using notify()), and reads the response (using read()). But it's hard to get it completely right in the general sense, because HTTP is a relatively complex protocol and the various implementations throw their own twists into the ring. Most of the burden is on the receiving side ("be flexible in what you receive"). Stunt uses the excellent Joyent HTTP parser to reliably and robustly parse both HTTP requests (if you're building a server) and HTTP responses (if you're building a client). The read_http() built-in function starts reading from a connection, and ultimately returns a MOO map with the headers, body, etc. or an error message, if the HTTP request/response was not valid.

The following simple verb illustrates how this works:

conn = open_network_connection("example.com", 80);
set_connection_option (conn, "hold-input", 1);
set_connection_option(conn, "binary", 1);
notify(conn, "GET /services/xyz/123 HTTP/1.1~0D~0A");
notify(conn, "Host: example.com~0D~0A");
notify(conn, "User-Agent: MOO/1.0~0D~0A");
notify(conn, "Accept: application/json~0D~0A");
notify(conn, "Connection: close~0D~0A");
notify(conn, "~0D~0A");
response = read_http("response", conn);
boot_player(conn);
return response;

The returned response is a map with elements for HTTP status, headers, body, etc.

["status" -> 200, "headers" -> ["Content-Type" -> "application/json", "Content-Lenght" -> "122", ...], "body" -> "{\"one\":1,\"two\":2,...}"]

Indirect Integration

Rather than writing any code at all, you can delegate to an external executable like curl. This has the advantage of broader protocol support (curl handles HTTPS, FTP, etc. in addition to HTTP), proxy support, etc. etc. The main disadvantage is that it only returns the body of the response.  However, at the expense of adding a dependency on an external command, it's the easiest way to simply hit an external endpoint to get data.

The following command illustrates how it works:

parse_json(exec({"curl", "http://example.com/services/xyz/123"})[2])

It only returns the body of the response:

["one" -> 1, "two" -> 2, ...]