Sling shot to CouchDB
Dang. Are we seeing the n signs of apocalypse in the world of computing lately, or what? Just weeks ago, Cocoa Touch seemed to have a lead in software that no one could catch up to unless they went back in a time machine to the mid-90s and persuaded Sun not to gut the AppKit-ish Internet Foundation Classes to make way for Swing, and then later use those as a base for a touchscreen API like Apple did with Cocoa—but it turns out that the Pre can do all of those multitouch acrobatics in a kiosk-mode web browser. Who knew that its ‘OS’ would be a Java freaking webapp, probably the least auspicious software tech a few years ago? Crazy stuff.
It’s the internet doing its disruptive thing again. Maybe there isn’t a need for beautiful user-interface libraries like Cocoa when millions of people are hacking away in JavaScript and the best code bubbles up until one day clueless old Palm is beating Apple at their own game with a decent multitasking interface that Apple couldn’t be bothered to invent. (Like Switcher?)
And that’s on the ‘desktop’. The best thing a web webapp’s server-side code can do is stay out of the browser interface’s way. Forget duplicating the features of JavaScript libraries elsewhere—you can’t even build bridges to them fast enough. For an application to keep pace with the web, it must splice itself with the rapidly evolving JavaScript genome.
But don’t GET
too comfortable
That’s not to say that application servers have no place. Even though CouchDB enables cool client-only applications, you do often need to alter a document before serving it. News feeds can not expect a JavaScript runtime. And there’s no telling with Google’s crawler, but resource locations that hide their content behind ajax requests aren’t exactly begging for a top pagerank.
CouchDB developers are aware of this:
By taking advantage of these characteristics pure-CouchDB applications can reach near parity with traditional server-heavy approaches. Notable exceptions include the ability to render dynamic text that isn’t JSON, or return results that require multiple queries against CouchDB.
And so there is _external
for server-side document processing, which evaluates JavaScript on the server that is stored in CouchDB documents. Thus, applications with these highly typical needs can remain ‘pure’ CouchDB, be entirely JavaScript, be deployed with only one big moving part, and be easily cloned from top to bottom with database replication. It’s all about extending the JavaScript copy-hack-share ecosystem onto the server.
Which is swell, but if the word pure doesn’t give you pause you can’t have been watching computing history for very long. Technologies that strive for purity tend to live clean, short lives. Purity doesn’t permit integration with other languages and runtimes—where innovation has not stopped, by the way. User experience is only one facet of computing, and JavaScript is uncompetitive in many others. The pure application that eschews any process outside client and server JavaScript VMs may look and act pretty, but it will lose to the application that does those things but also benefits from some amazing new data synthesis library, or whatever. As long as the impure application also swims in the waters of client-side JavaScript, its only disadvantage is a more complex configuration.
A more complex configuration
So we want a web application that does some work on the server, but that also allows unlimited client activity including direct access to the datastore. Why? Because if you’re not doing that you’re not benefiting from the true ultimate power of CouchDB. It is necessary to do this, for giggles. Even if you do something like accessing the REST-base through ssh port forwarding (a poor programmer’s admin login), you’re still having fun while keeping your data safe.
There’s a problem with this approach, however! Can you guess it? Or, like this writer, would you have gotten all the way to testing it before palm smacking your forehead? Well, is your get out of palm-smacking free card: same origin policy.
In most browsers you can’t even PUT
to a different port from the one the page loaded on, can’t even kick things around on localhost. For a webapp to talk to CouchDB or anything else, it has to appear to the browser that everything is on the same machine and port. So you need a reverse-proxy. Which almost everyone does for live sites anyway, but for development? That seems cruel and unusual! It’s not that bad tho:
<IfModule proxy_http_module>
ProxyPass /couch/ http://127.0.0.1:5984/
ProxyPass /script/ http://127.0.0.1:5984/_utils/script/
ProxyPass / http://127.0.0.1:8080/
</IfModule>
On recent versions of Mac OS or Ubuntu (et al.), Apache and the needed modules are only a few commands away. A hybrid webapp serves itself on port 8080 and you access it on 80, including Couch (under /couch
) and the JavaScript it comes with (under /script
). Set it and forget it.
Ready, aim, fire?
This is all for a Scala application called Sling. It presents a tranformed view of a CouchDB database. For an incoming request such as GET /friday/About
it presents the same document that its CouchDB would, but formatted for passive rendering. The document must have a body
property, which is converted from Markdown to HTML. The database is expected to have a document named style.css
, to customize its appearance. Before the body content Sling lists and links all the documents in the database other than the stylesheet.
Sling the Friday site
Sling’s toolchain is wack. Requests from the client are handled in a Slinky application, which talks to Couch through Databinder Dispatch’s HttpClient wrapper and converts Markdown to HTML with Showdown interpreted by Rhino. The code is also crazy looking:
request match {
case Path(DbId(db, id)) =>
val couched = [...]
couched(id) {
case (OK.toInt, ri, Some(entity)) =>
id match {
case ("style.css") =>
Some(cache_heds(ri, OK(ContentType, "text/css; charset=UTF-8")) <<
new Store(entity.getContent())(PageDoc.body).mkString("").toList
)
[...]
case (id) =>
Some(cache_heds(ri, OK(ContentType, content_type)) << strict << doc(
Some(couched), id, md2html(new Store(entity.getContent())(PageDoc.body).mkString(""))
))
}
case (NotModified.toInt, ri, _) => Some(cache_heds(ri, NotModified))
case (NotFound.toInt, _, _) => None
}
A Slinky app can pattern-match incoming requests against anything, which should be flexible enough hm? DbId
is an object with an unapply
function that attempts to pull the database name and document id from the requested path, also converts underscores to spaces in the id. The couched
value is set to a Databinder Dispatch request wrapper, in some (ugly—pls fix) code that passes along any IfNoneMatch
header. CouchDB apparently does everything with ETags, which is annoying because they’re not as well supported or easy to use as Last-Modified, but they’ll have to do.
Then couched(id)
executes the GET
and passes the response code and some HttpClient stuffs to our pattern matching function. When CouchDB replies OK
and it was the stylesheet, we pull that from the JSON with some Databinder Dispatch code and send it out the wire. If it’s any other document it is passed through md2html
(which calls Showdown) and then passed to a doc
function which just piles some HTML around it in an uninteresting way. If the request had an ETag we may get NotModified
back from Couch, in which case that is passed along, or None
if it was NotFound
.
In the end it’s just a regular web page and nobody knows you are a crazy person for serving it this way, unless you write about it on a blog. Because it speaks in cache headers, the speed of fetching the document and transforming it is practically irrelevant; shove it behind mod_cache
and it may as well be a static html file.
So why not just use HTML files, fool?
No kidding, this is pretty silly so far! Who cares if you can serve pages like this if they are still a pain to edit. But what if they are a joy to edit?
Edit the Friday site
(Click on the ship to blast off to editing space. Do it.)
How does that work? Basicamente it’s the same page as before but it inserts the JSON straight from Couch (view the marked-down source) and the client does the transformation with Showdown and JQuery—which is the last element of the Sling toolchain, unmentioned until now so as not to spoil the surprise. JQuery powers the fancy trip to editing space, keeps the content in sync with the textbox, and finally puts the changes back on CouchDB as directed by edit.js
:
$(function() {
$('#body').val(doc.body);
$('#body-preview').html(makeHtml(doc.body));
$('#body').keyup(function() {
$('#body-preview').html(makeHtml($(this).val()));
});
$('#shade').click(function() {
if ($('#edit').is(':hidden')) {
$('#edit, #fixed').slideDown(function() {
$('#shade').attr('src', '/css/ship-left.gif');
});
}
else {
$('#edit, #fixed').slideUp(function() {
$('#shade').attr('src', '/css/ship-up.gif');
});
}
});
$('#form').submit(function() {
doc.body = $('#body').val();
$.ajax( {
type: 'PUT',
url: doc_url,
dataType: 'json',
data: JSON.stringify(doc),
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert('Save failed: ' + textStatus);
},
complete: function(req) {
doc._rev = $.httpData(req, 'json').rev;
}
} );
$('#shade').click();
return false;
} );
});
Anyone can view the edit page, so someone could type in something bad, preview the hilarious results, and save it for all the world to enjoy right? Ha ha no in the live server config /couch
is protected:
ProxyPass /{database} ajp://localhost:{sling-port}/{database}
<Location /couch/>
AuthName "{domain name}"
AuthUserFile /etc/apache2/htpass
AuthType Digest
Require valid-user
</Location>
ProxyPass /couch/ http://127.0.0.1:5984/
ProxyPass /script/ http://127.0.0.1:5984/_utils/script/
When JQuery tries to PUT
the browser will ask for a name and password. Sorry. But at least you got to play on the edit page! If handled entirely on the client, with JavaScript fetching the JSON instead of it being served inline, the authentication rules would have to be a little more precise to allow unauthorized play. But the code was already written to pull the doc on the server side so why not.
Static shock
This application doesn’t do anything amazing with JVM libraries so why isn’t it written for Couch’s _external
, in JavaScript? It certainly could have been. If JavaScript is your favorite language that is the way to go. But a lot of people prefer other languages, even languages that can be statically compiled.
Build scripts are one area where everyone seems resigned to dynamic interpretation. From makefiles
to Ant’s build.xml
, builders work off instructions they don’t check out very much until actually trying to execute them. Non-experts don’t know for sure if they’ve coded something the right way until they see some clean builds and a couple kinds of partial builds.
But now there is a tool for programmatically defining a project build in Scala, called sbt for “simple build tool.” Sling is built by sbt. It’s a pretty demanding build, jumping through these hoops and more, but sbt handles it with aplomb. (It’s also fast, which deserves a post of its own.)
You guys aren’t even ready for how much easier sbt is than dynamic build tools. It’s not so much that your build definition is type-checked, but that the structure forces the API itself to be explicit. Rather than having entrypoints that accept any key-value pair you want to pass it (which may result in something, nothing, or fail), sbt has to declare exactly what it can handle. This leads to many entrypoints and—so it feels to Coderspiel—shockingly better usability. If you’re wondering how to specify a main class for the run
action, when you come across def mainClass
the mystery is over. It’s the only and best way to do it. Using overridable methods for configuration, paired with Scala’s mandatory override
keyword, is foolproof.
That is to say, applied type theory seems to be finally coming into its own; this is no time to throw down our type declarations and surrender to JavaScript everywhere.
By the way the n + 1 sign of the apocalypse is Sun getting its client-side JVM act together, so, no worries there.
Codercomments
Just to be clear, the greatness of the Pré is not that it runs things written in HTML, CSS and JavaScript. It’s the interface. If Apple just up and up stole the card switcher, notification area, gestures and multiple-cards-per-app possibility for Cocoa Touch, they’d be winning again because you could write better UIs in it.
Pré’s not good because you can write everything in HTML, CSS and JavaScript. It’s good despite writing everything in HTML, CSS and JavaScript, because it heavily scopes the meaning of “everything”. No useful 3D, no native (or native-like) code. Doing Rolando might be possible at about 7 fps with some serious canvas element chops, but Super Monkey Ball or Crayon Physics? No.
Also, the email address validator in this form apparently isn’t validating email addresses, because I’ve got one and it’s got a ’+’ in it and it’s not accepted.
Sure, whatever. This is a postmodernist weblog.
Heh, I worked on something similar with XForms and eXist.
A couple of days ago you had a non sequitur of “damn you FCKEditor” … I totally feel your pain :)
Oh, but the eventual defeat of wysinwyg web editors has everything to do with showdown.js!
oh… uh… I didn’t bother to read the entry :) Yes that would have been useful to me 3 years ago.
Probably not 100% accurate or complete, but as an appendix:
36 lines of javascript 32 lines of configuration 62 lines of Scala (build) 168 lines of Scala (main) 30 lines of css 13 dependencies (jars)
key-value pairs? Like -Dexec.mainClass=net.databinder.sling.Server?
Anyway, thanks for the mention! I’m glad you found sbt suitable for your purposes.
Key value pairs, like
Do you think this application is too… complicated?
Ah, a buildr thing. You’re right. I agree!
It was intended to be a positive comment; I like to see neat things done with a small amount of code and reusing a lot of work done by others.
Perhaps neat is relative, but each technology was new to me. (I’m not a web developer, although I put together toy webapps occasionally.) My point was that I think you got a lot out of your lines of code. Well-leveraged.
Oh, and yes, the rocket ship was cool.
Thanks very much! I would like to get some of those numbers down, still. App.scala has room for improvement, in l.o.c. and clarity. And dependency wise, HttpClient brings in a number of jars, it being so aggressively modularized. This app could get by with a plain URLConnection, but my own HTTP wrapper uses HttpClient for its more advanced features (digest authentication) in other apps. And, eventually, I expect CouchDB will support digest auth as well. So, maybe 13 jars is the magic number.
This quote is Old News: “Notable exceptions include the ability to render dynamic text that isn’t JSON, or return results that require multiple queries against CouchDB.”
It was true at the time, but now there are integrated functional approaches for rendering views and documents via any language supported by a design document.
My site http://jchrisa.net is just a CouchDB proxied to port 80. Works halfway decent even with JS turned off.
Three Months Old, yes, but it was the only prose I could find on the subject! At 25 Days Old, even this post is no spring chicken. But if you write about these new integrated functional approaches, I’ll try to link to them while they are still New News.
Add a comment