Re: Factor

Factor: the language, the theory, and the practice.

Hello, web!

Saturday, August 28, 2010

#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 https://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="https://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 https://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.