Hello, web!
Saturday, August 28, 2010
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:
- http.server, the web server
- furnace, the web framework
- db, the database support
- html.templates (both chloe and fhtml)
- html.forms and form validators
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
.