Saturday, August 28, 2010

Hello, web!

One thing that surprises many people when they come to Factor, is that a lot of the Factor infrastructure (main site, planet, pastebin, documentation, and wiki) is written in Factor, and runs on a Factor web server.

The Factor web server is very capable, supporting static files, CGI scripts, SSL authentication, session management, and dynamic web pages. Some of the vocabularies that are involved:

Hello, world!

This is a simple application that returns a plain text page that says "Hello, world!". Our web application is structured into a dispatcher (our "main responder"), an action, and words to create and run the web server.

USING: accessors furnace.actions http.server
http.server.dispatchers http.server.responses io.servers kernel
namespaces ;

IN: webapps.hello

TUPLE: hello < dispatcher ;

: <hello-action> ( -- action )
    <page-action>
        [ "Hello, world!" "text/plain" <content> ] >>display ;

: <hello> ( -- dispatcher )
    hello new-dispatcher
        <hello-action> "" add-responder ;

: run-hello ( -- )
    <hello>
        main-responder set-global
    8080 httpd wait-for-server ;

MAIN: run-hello

Run the code by calling run-hello, then navigate to http://localhost:8080 and you will see the response.

Templates

To begin experimenting with templates, lets change the logic to include a form where a name can be provided. We will create a Chloe template file. Let's create a hello.xml file in the same location as the webapps.hello vocabulary:

<?xml version='1.0' ?>

<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">

    <t:form t:action="$hello">
        <label>What is your name?</label>
        <t:field t:name="name" />
        <input type="submit" />
    </t:form>

</t:chloe>

Now, modify the hello-action to load the template. The default form submission is via POST and can be supported using the submit slot of the action. We respond to a form submission by returning a plain text response saying "Hello, $name!":

USE: formatting

: <hello-action> ( -- action )
    <page-action>
        { hello "hello" } >>template
        [
            "name" param "Hello, %s!" sprintf
            "text/plain" <content>
        ] >>submit ;

When you navigate to http://localhost:8080, you will see a simple form prompting you to type in a name. After submitting the form, you will see a customized response depending on the name provided.

Form Validation

It is frequently useful to validate parameters that are submitted via forms (e.g., for numbers, e-mail addresses, ranges, required or optional, etc.). To support this, we need to add validation logic for every parameter desired (using words from the validators vocabulary). In this case, the name should be a required parameter:

USE: validators

: <hello-action> ( -- action )
    <page-action>
        [
            { { "name" [ v-required ] } } validate-params
        ] >>validate
        { hello "hello" } >>template
        [
            "name" value "Hello, %s!" sprintf
            "text/plain" <content>
        ] >>submit ;

Next, wrap the dispatcher in an <alloy>, which provides support for session-persistence, form validation, and database persistence.

USE: furnace.alloy
USE: db.sqlite

: <hello> ( -- dispatcher )
    hello new-dispatcher
        <hello-action> "" add-responder
     "resource:hello.db" <sqlite-db> <alloy> ;

If you navigate to the website now, and don't provide a name, you'll be redirected back to the form with the validation error specified.

Other tips

There is a development? symbol that can be set to t to make sure the web server is running the latest code from your application and that errors generate nice stack traces.

Malu has a nice tutorial on Github about building a blog application in Factor.

All of the Factor websites (as well as some nice examples like a "counter", "todo list", "tiny url", and "ip address") are in resource:extra/webapps.

9 comments:

Unknown said...

Hi,

Thank you, for a concise presentation of web development with factor. I didn't get the database persistence part working. Am I missing something obvious here?

mrjbq7 said...

@Peter: When you say "database persistence", I assume you mean the form validation? That just requires the use of "v-required" and "validate-params" and the addition of the "<alloy>" call. It seems to work for me?

The form validation uses the database to persist validation errors across requests. By adding the "<alloy>", you can now also add other kinds of persistence -- maybe tracking the first time each name said hello, and adding a "We first met N seconds ago" message. That might be a simple way to learn database persistence. The Factor documentation has a db.tuples tutorial that seems like a good place to start.

Unknown said...

@mrjbq7: when I run the example as written in the blog, I get an "The image refers to a library or symbol that was not found at load time" error. If I remove the line with reference to the database ""resource:hello.db" " it works fine. I got the same error in db.tuples tutorial.

mrjbq7 said...

@Peter: You are probably running on Windows? Factor doesn't come with the DLLs for SQLite, but you can install it if you want -- either from the SQLite project, or download the 32-bit or 64-bit DLLs (you want "sqlite3.dll"). If you copy that to your Factor directory, it should work.

Unknown said...
This comment has been removed by the author.
Unknown said...

@mrjbq7: Yes I'm running factor on Windows and that solved the problem. Thank you!

Ryan said...

This is probably a Factor-newbie mistake, but perhaps you can help. When running your first example in the listener, everything works fine, but when I try to save it as a file in work/webapps/hello/hello.factor and run it from the command line with `./factor -run=webapps.hello`, I get the error below. What I'm trying do is run a web server in Factor from the command line in the foreground, so is there is another way, I'd be happy to hear suggestions. Thanks!

Quotation's stack effect does not match call site
quot [ \ run-hello execute ]
call-site ( -- )
(U) Quotation: [ c-to-factor -> ]
Word: c-to-factor
(U) Quotation: [ [ catchstack* push ] dip call -> catchstack* pop* ]
(O) Word: command-line-startup
(O) Word: wrong-values
(O) Method: M\ object throw
(U) Quotation: [
OBJ-CURRENT-THREAD special-object error-thread set-global
current-continuation -> error-continuation set-global
[ original-error set-global ] [ rethrow ] bi
]

mrjbq7 said...

@Ryan: Sorry about that! Every "main method" must have a stack effect of "( -- )", meaning takes no inputs and gives no outputs. In my case, the "run-hello", produces a "http-server" object which caused the traceback you see. I updated the example to use "wait-for-server" which will block until the server finishes serving connections. Can you try again?

Thanks!

Ryan said...

@mrjbq7, thanks that works much better!

Now that I got it working, I just put together a Heroku buildpack for Factor that uses your example (with a link back for credit, of course). Check it out if you want to run Factor apps on Heroku: https://github.com/ryanbrainard/heroku-buildpack-factor

Thanks again for the help!