I’ve been wingeing quite a lot recently about AppJet’s demise and the shoddy state of potential replacements, and got quite a nice response from Jim Pick at Joyent. I decided today to have a play with their new Smart Platform and had a go at porting one of my AppJet applications over to Smart. Here’s my notes, fresh from the lab…
It’s not as simple as hitting an edit button yet, but it’s still worth having a play. The use of Sammy to handle and dispatch incoming requests, along with a single “bootstrap.js” file that controls all the SSJS, means that it ought to be fairly easy to port over single-page AppJet apps to Smart Platform. Here’s some notes I’ve gathered whilst porting over an application that acts as a gateway between tarpipe’s REST connector and a Yahoo! Pipe, allowing you to send data to a Yahoo! Pipe for processing.
Request object
In AppJet, you get the POSTed variables and the query-string parameters both in request.params. In Smart, the query-string parameters are in request.query with the raw string in request.queryString; POSTed variables are in request.body with the raw datastring in request.content.
Dispatching
In AppJet, you have the option to define different functions for handling different types of request e.g. GETs, POSTs and requests to different resources e.g. /this, /that. Smart uses Sammy, which gives you a similar mechanism to choose which functions respond to different incoming requests, although you also have the option to use a single function main(request) {...} to handle all responses. A concrete example is:
AppJet:
function get_resource() { ... }
Smart:
GET("/resource", function() { });
Writing a response
AppJet provides a default chrome that wraps an application, which you can remove by setting page.setMode('plain'); then you use functions like print(response) to send the respons. Smart does it slightly differently, where whatever you return from a function is sent as the response. It doesn’t seem possible to send HTTP header information.
Making HTTP requests
AppJet provides functions like wpost and wget to make HTTP calls. Smart provides the system.http.request function to do the same sort of thing. The difference in the returned response is that AppJet gave you a raw string of the response body, whereas Smart returns an object containing headers and content properties.
Deployment
The lack of an in-browser edit experience has been my complaint of late, but doing the Git bit to deploy my app to Smart was straightforward, and now it’s up at http://f7e3dbf.smart.joyent.com.
Try it out with:
curl -d 'pipeId=lAUEoB1R3hGTqt6hggSecQ&data={"items":[{"title":"A title","description":"Any description","link":"http:\/\/example.com"}]}' http://f7e3dbf.smart.joyent.com
Code
For the sake of completion, here’s the source code. I’ve gone from this:
/* appjet:version 0.1 */
import("lib-json");
var p = request.params;
var result = "";
var output = "";
function serialize(obj) {
var str = "";
for(var i in obj) {
str += "&"+encodeURIComponent(i)+"="+encodeURIComponent(obj[i]);
}
return str.substring(1);
}
if(p && p.data && p.pipeId) {
var data = JSON.parse(p.data);
var pipeId = p.pipeId;
var items = data.items;
if(items) {
var url = "http://pipes.yahoo.com/pipes/pipe.run?_id="+pipeId+"&_render=json";
// tarpipe only POSTs 1 item
var result = wpost(url,{title:items[0].title,description:items[0].description});
if(result) {
result = JSON.parse(result);
if(result) {
output = result.value;
if(!output.items[0].title) {
output.items[0].title = "returned from Yahoo! Pipe at: "+pipeId;
}
output = JSON.stringify(output);
} else {
result = "bag result from Yahoo! Pipe at: "+pipe;
}
} else {
result = "no result returned";
}
} else {
result = "no items in data input";
}
} else {
result = "no data or no pipe input";
}
page.setMode('plain');
output ? print(output) : print(result);
To this:
system.use("com.joyent.Resource");
system.use("org.json.json2");
function serialize(obj) {
var str = "";
for(var i in obj) {
str += "&"+encodeURIComponent(i)+"="+encodeURIComponent(obj[i]);
}
return str.substring(1);
}
function main(request) {
var p = request.content!="" ? request.body : request.query;
var result = "";
var output = "";
/* for debug input
for (var i in p) {
output += i+", "+p[i]+"\n";
}
return output;*/
if(p && p.data && p.pipeId) {
var data = JSON.parse(p.data);
var pipeId = p.pipeId;
var items = data.items;
if(items) {
var url = "http://pipes.yahoo.com/pipes/pipe.run?_id="+pipeId+"&_render=json";
// tarpipe only POSTs 1 item
var toPost = "title="+items[0].title+"&description="+items[0].description;
var result = system.http.request("POST",url,null,toPost);
if(result && result.content) {
result = JSON.parse(result.content);
if(result) {
output = result.value;
if(!output.items[0].title) {
output.items[0].title = "returned from Yahoo! Pipe at: "+pipeId;
}
output = JSON.stringify(output);
} else {
result = "bag result from Yahoo! Pipe at: "+pipe;
}
} else {
result = "no result returned";
}
} else {
result = "no items in data input";
}
} else {
result = "no data or no pipe input";
}
return output || result;
}

2 Comments
Just a quick note regarding headers – it is definitely possible to set them.
If you’re using Sammy simple set a key/value pair in this.response.headers from within a handler. If you’re working further down the stack simply include it in the returned http response.
(http://smart.joyent.com/docs/basics.html explains the format)
Thanks James.