You are looking at historical revision 19183 of this page. It may differ significantly from its current revision.
- A "Hello, world!" example
- Accessing request variables
- Re-evaluating the code by reloading the page
- Using ajax
- Database access
- Login pages and session
- Session inspector
- Web REPL
- Pages access control
- Compiled pages
- Multiple applications support
- List of user configurable parameters
- List of read-only parameters available to users
- List of procedures
- Tips and tricks
- Use link and form for links and forms
- Use with-request-vars when referencing the same request variable multiple times
- Use the web REPL and the session inspector for debugging
- Create custom page definers when page-template and/or plain define-page are not enough
- Debugging: error messages on the browser window
- Run awful without arguments to quickly share a file
- Awful badge
- The name
- FAQ (aka Fakely Asked Questions)
- Known bugs and limitations
- Version history
awful provides an application and an extension to ease the development of web-based applications.
Here's a short summary of awful features:
- Straightforward interface to databases (currently supported are postgresql and sqlite3)
- Page dispatching via regular expressions
- Easy access to query string and body request variables from HTTP requests
- Ajax support via JQuery
- Reasonably flexible (several configuration parameters)
- Compiled pages made easy
- Session inspector
- Web REPL
The following eggs are required:
awful is composed by two parts: an application, which can be thought as a web server; and an extension, which provides most of the supported features.
A "Hello, world!" example
Here's a "Hello, world!" example to be run by awful (hello-world.scm).
(use awful) (define-page (main-page-path) (lambda () "Hello, world!"))
To run this example, execute:
$ awful hello-world.scm
Then access localhost:8080 using your favourite web browser.
Without any configuration, awful listens on port 8080. Since awful uses Spiffy behind the scenes, you can configure the web server parameters using Spiffy's.
define-page is the primitive procedure to define pages. In the simplest case, it takes as arguments a path to the page and a procedure to generate the page contents. The path to the page you use as the first argument is the same to be used as the path part of the URL you use to access the page.
In the example we use the main-page-path parameter, which is one of the awful configuration parameters. The default is "/".
If you look at the page source code, you'll see that awful created an HTML page for you. Awful uses the html-tags's html-page procedure behind the scenes. You can customize the page either by passing html-page's keyword parameters to define-page or by setting the page-template parameter with your favourite procedure to generate pages. page-template is a one-mandatory-argument procedure which receives the page contents and returns a string representing the formatted contents. define-page would still pass the html-page keyword parameters, so you can make use of them (the page-template procedure should be defined considering them -- if you don't need them, just ignore them by defining (lambda (contents . args) ...)).
You can also use some global page-related parameters if all pages use the same CSS, doctype and charset (page-css, page-doctype and page-charset, respectively).
An alternative way to write Awful scripts is by using the #!/path/to/awful shebang line. Example:
#! /usr/bin/awful (use awful) (define-page (main-page-path) (lambda () "Hello, world!"))
Then you just need to run your script (assuming the file has execution permissions):
Accessing request variables
Awful provides a procedure ($) to access variables from the request, both from the query string (GET method) and from the request body (e.g., POST method).
Here's a modified "Hello, world!" example to greet some person instead of the whole world:
(use awful) (define-page (main-page-path) (lambda () (++ "Hello, " ($ 'person "world") "!")))
The ++ procedure is an alias to string-append (name inspired by Haskell's operator to concatenate strings).
So, restart the web server to reload the code, then access the main page using an argument, represented by the person query string variable: http://localhost:8080/?person=Mario. You'll see a page showing Hello, Mario!.
Re-evaluating the code by reloading the page
When we upgraded our "Hello, world!" example to the improved one which can use an argument passed through the URL, we needed to modify the code and restart the web server to reload the application code. Awful provides a way to reload the code via URL without restarting the server. To do that, we can define a special page whose handler just reloads the code:
(define-page "/reload" (lambda () (load-apps (awful-apps)) "Reloaded"))
and restart the awful server. Now, whenever you want to reload the application code, access http://localhost:8080/reload.
You can control which IP numbers can access the reload page by using the page-access-control parameter. For example, allowing only the localhost to reload the apps:
(page-access-control (lambda (path) (if (equal? path "/reload") (member (remote-address) '("127.0.0.1")) #t)))
Awful provides a way to (hopefully) make the use of ajax straightforward for web applications.
When you have ajax enabled and you want to disable it for specific pages, you can pass #f as the value for the define-page keyword parameter no-ajax.
So, if we now change our code to
(use awful) (enable-ajax #t) (define-page (main-page-path) (lambda () (++ "Hello, " ($ 'person "world") "!")))
and reload the application, we'll have our page linked to JQuery.
Awful provides some procedures to do ajax. We start by the more generic one (ajax) to reply the page greetings when we click "Hello, <person>!".
(use awful html-tags) (enable-ajax #t) (define-page (main-page-path) (lambda () (ajax "greetings" 'greetings 'click (lambda () (<b> "Hello, awful!")) target: "greetings-reply") (++ (<a> href: "#" id: "greetings" (++ "Hello, " ($ 'person "world") "!")) (<div> id: "greetings-reply"))))
The ajax procedure uses at least four arguments:
1. The URL path to the server-side handler (a string). This path is relative to ajax-namespace parameter (default is ajax. So, in the example, we'll have "/ajax/greetings" as the ajax path to be generated if we pass "ajax" as the first argument.
2. The ID of the DOM element to observe.
3. The event to be handled
4. The procedure to be run on the server-side.
For the very specific case of creating links that execute server side code when clicked, awful provides the ajax-link procedure. So our example could be coded like:
(use awful) (enable-ajax #t) (define-page (main-page-path) (lambda () (++ (ajax-link "greetings" 'greetings (lambda () (<b> "Hello, awful!")) target: "greetings-reply") (<div> id: "greetings-reply"))))
To access databases, you need some of the awful eggs which provide database access. Currently, these are the possible options:
- awful-postgresql (for Postgresql databases, using the postgresql egg)
- awful-sqlite3 (for Sqlite3 databases, using the sqlite3 egg)
- awful-sql-de-lite (for Sqlite3 databases, using the sql-de-lite egg)
As with ajax, database access is not enabled by default. To enable it, you need to pick one the awful database support eggs and call the enable-db procedure. Since version 0.10, and differently from the enable-* parameters, enable-db is a zero-argument procedure provided by each of awful database-support eggs. So, if you use awful-postgresql, the enable-db procedure will automatically set up awful to use a postgresql database.
Additionally, to access the db, you need to provide the credentials. You can provide the credentials by setting the db-credentials parameter. See the documentation for the eggs corresponding to the database type you are using (postgresql for Postgresql and sqlite3 or sql-de-lite for Sqlite3.)
To actually query the database, there's the $db procedure, which uses as arguments a string representing the query and, optionally, a default value (a keyword parameter) to be used in case the query doesn't return any result. $db returns a list of lists. Below is an usage example:
(use awful awful-postgresql) (enable-db) (db-credentials '((dbname . "my-db") (user . "mario") (password . "secret") (host . "localhost"))) (define-page "db-example" (lambda () (with-output-to-string (lambda () (pp ($db "select full_name, phone from users"))))))
Hint: for Sqlite3 databases, db-credentials should be the path to the database file.
There's also the $db-row-obj procedure for when you want to access the results of a query by row name. $db-row-obj returns a procedure of two arguments: the name of the field and, optionally, a default value to be used in case the field value is #f.
(define-page "db-example" (lambda () (let ((& ($db-row-obj "select full_name, phone from users where user_id=1"))) (<p> "Full name: " (& 'full_name)) (<p> "Phone: " (& 'phone)))))
Warning: currently $db-row-obj is only implemented for Postgresql.
If you need more flexibility to query the database, you can always use the (db-connection) parameter to get the database connection object and use it with the procedures available from the your favorite database egg API.
Login pages and session
Awful provides a very basic (awful?) support for creating authentication pages.
The basic things you have to do is:
1. Enable the use of sessions:
2. Set the password validation parameter. This parameter is a two argument procedure (user and password) which returns a value (usually #t or #f) indicating whether the password is valid for the given user. The default is a password which returns #f. Let's set it to a procedure which returns #t is the user and the password are the same:
(valid-password? (lambda (user password) (equal? user password)))
3. Define a login trampoline, which is an intermediate page accessed when redirecting from the login page to the main page.
4. Create a login page. Awful provides a simple user/password login form (login-form), which we are going to use. Here's our full example so far (using the basic "Hello, world!" as main page).
(use awful) (enable-session #t) (define-login-trampoline "/login-trampoline") (valid-password? (lambda (user password) (equal? user password))) (define-page (main-page-path) (lambda () "Hello world!")) (define-page (login-page-path) (lambda () (login-form)) no-session: #t)
That's the very basic we need to set an auth page. If the password is valid for the given user, awful will perform a redirect to the main page (main-page-path parameter) passing the user variable and its value in the query string . If the password is not valid, awful will redirect to the login page (login-page-path parameter)) and pass the following variables and values:
- the reason why awful redirected to the login page. It may be invalid-password, for when the password is invalid for the given user; or invalid-session for when the session identifier is not valid (e.g., the session expired).
- the URL path to page the user tried to access, but couldn't because either he/she was not loged in or he/she provided an invalid session identifier.
- the user used for the form user field.
Now we're gonna change our main page to store the user in the session and retrieve it to make the greetings message:
(define-page (main-page-path) (lambda () ($session-set! 'user ($ 'user)) (++ "Hello " ($session 'user "world") "!")))
Here we can see the two procedures to access the session: $session and $session-set!.
$session-set! accepts two arguments: the first one is the name of the session variable and the second one is its value.
$session takes the name of the session variable as argument and returns its session value. We can optionally use a second argument to specify a default value, in case the session variable is not bound or is #f.
Awful provides a session inspector, so we can easily see the session contents for a given session identifier. By default, the session inspector is disabled. We can enabled it using the enable-session-inspector procedure, passing the session inspector URL path as argument:
Now, if you log in and try to access http://localhost:8080/session-inspector, you'll get ... an access denied page.
Awful provides a way to control access to the session inspector (session-inspector-access-control parameter). The session-inspector-access-control parameter is an one-argument procedure which returns #f or some other value to indicate whether the access to the session inspector is allowed or not. By default, it blocks all access. Let's configure it so we can access the session inspector from the local machine (whose IP number is 127.0.0.1):
(session-inspector-access-control (lambda () (member (remote-address) '("127.0.0.1"))))
Regarding to the access denied message, you can customize it by setting the session-inspector-access-denied-message.
Now we can access http://localhost:8080/session-inspector and see the session contents.
Note: if enable-session-cookie is #f, you need to pass the session identifier in the query string (e.g., http://localhost:8080/session-inspector?sid=the-session-cookie-here).
Here's a screenshot:
For further run-time, server-side web hacking, awful provides a REPL that you can use via web browser. The activation and control access are basically the same as for the session inspector. The relevant procedure and parameters are:
Here's a screenshot:
Pages access control
To allow/deny access to pages, you can use the page-access-control parameter. It's an one-argument procedure (the page path) which can be set to determine if the access to the page is allowed or not.
The example bellow shows a very silly access control to the main page: it only allows the access when the value of the request variable user is "mario":
(use awful) (enable-session #t) (define-login-trampoline "/login-trampoline") (valid-password? (lambda (user password) (equal? user password))) (page-access-control (lambda (path) (or (member path `(,(login-page-path) "/login-trampoline")) ;; allow access to login-related pages (and (equal? ($ 'user) "mario") (equal? path (main-page-path)))))) (define-page (main-page-path) (lambda () "Hello world")) (define-page (login-page-path) (lambda () (login-form)) no-session: #t)
You can customize the access denied message by setting the page-access-denied-message with an one-argument procedure (the page path).
Since Chicken is a compiler and our pages are Chicken code, we can compile them to have faster pages. We just need to compile our app and pass the generated .so to the awful application:
$ csc -s hello-world.scm $ awful hello-world.so
Multiple applications support
To be able to deploy multiple awful applications with different configurations under the same server, use Spiffy access files. See Spiffy's access files documentation for further details.
List of user configurable parameters
Debugging[parameter] (debug-file [file path])
If #f, indicates that debugging should be disabled. When set to a string, it should be the path to the file where the debug messages go (when debug or debug-pp is used.)
The default value is #f.[parameter] (debug-db-query? [boolean])
When not #f, all queries passed to $db and to $db-row-obj are printed to the debug file.
The default value is #f.[parameter] (debug-db-query-prefix [string])
Prefix to be used for queries debugging when debug-db-query is not #f.
The default value is "".[parameter] (debug-resources [boolean])
When #t, enables debugging of awful's resources table (an alist mapping paths (or regexes) and vhost paths to their corresponding procedures to be executed on the server side upon request). The debugging data is sent to the file pointed by debug-file. The default value is #f.
Database[parameter] (db-credentials [boolean or list])
Credentials to be used to access the database (see the documentation for the egg corresponding to the database backend you selected.) When #f, no database access is performed.
The default value is #f.
Ajax[parameter] (ajax-library [string])
URL or path to the ajax library (currently only JQuery is supported.)
The default value is "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"[parameter] (enable-ajax [boolean])
When #t, makes define-page link the ajax-library to the generated page.
The default value is #f[parameter] (ajax-namespace [string])
Name to be used as a namespace for ajax URL paths.
The default value is "ajax".[parameter] (ajax-invalid-session-message [string])
The message to be used when attempting the make an ajax call using an invalid session identifier.
The default value is "Invalid session".
Sessions[parameter] (enable-session [boolean])
When #t, session support is enabled.
The default value is #f.[parameter] (enable-session-cookie [boolean])
The name of the cookie for storing the session identifier. The dafult value is "awful-cookie".
Access control[parameter] (page-access-control [procedure])
An one-argument (URL path of the current page) procedure which tells whether the access to the page is allowed or not.
The default value is (lambda (path) #t).[parameter] (page-access-denied-message [procedure])
An one-argument (URL path of the current page) procedure which returns the access denied message.
The default value is (lambda (path) (<h3> "Access denied.")).[parameter] (valid-password? [procedure])
A two-argument (user and password) procedure which indicates whether the given password is valid for the given user.
The default value is (lambda (user password) #f).
Pages[parameter] (page-doctype [string])
The doctype (see the doctype egg) to be applied to all pages defined by define-page. Can be overwritten by define-page's doctype keyword parameter.
The default value is "".[parameter] (page-css [boolean or string])
The CSS file to be linked by all pages defined by define-page. Can be overwritten by define-page's css keyword parameter. See html-utils's html-page procedure to know about the css keyword parameter syntax.
The default value is #f (no CSS).[parameter] (page-charset [boolean or string])
The page charset to be used by all pages defined by define-page. Can be overwritten by define-page's charset keyword parameter. The default value is #f (no explicit charset).[parameter] (page-template [procedure])
An one-mandatory-argument procedure to be used by define-page (unless define-page's no-template keyword parameter is set to #f) to generate HTML pages. Although this procedure can take only one mandatory argument, the following keyword arguments are passed:
The default value is html-page (see the html-utils egg documentation.)[parameter] (page-exception-message [procedure])
An one-argument procedure to be used when an exception occurs while define-page tries to evaluate its contents.
The default value is (lambda (exn) (<h3> "An error has accurred while processing your request."))
Page paths[parameter] (main-page-path [string])
The URL path to the app main page.
The default value is "/".[parameter] (app-root-path [string])
The base path to be used by the application. All the pages defined by define-page will use app-root-path as the base directory. For example, if app-root-path is set to "/my-app" and "my-page" is used as first argument to define-page, the page would be available at http://<server>:<port>/my-app/my-page.
The default value is "/".[parameter] (login-page-path [string])
The URL path for the login page. When creating a login page, be sure to set the no-session keyword parameter for define-page to #t, otherwise you'll get an endless loop.
The default value is "/login".
Web REPL[parameter] (web-repl-access-control [procedure])
A no-argument procedure to control access to the web REPL.
The default value is (lambda () #f).[parameter] (web-repl-access-denied-message [string])
Message to be printed when the access to the web REPL is denied.
The default value is (<h3> "Access denied.").
Session inspector[parameter] (session-inspector-access-control [procedure])
A no-argument procedure to control access to the session inspector.
The default value is (lambda () #f).[parameter] (session-inspector-access-denied-message [string])
Message to be printed when the access to the session inspector is denied.
The default value is (<h3> "Access denied.").
The default value is the identity procedure.
List of read-only parameters available to users
Note: these parameters should not be explicitly set and when their use is needed, it's a string sign you're doing something you shouldn't (except for db-connection, which can be used by procedures from the postgresql egg API).[parameter] (http-request-variables)
The per-request value returned by spiffy-request-vars's request-vars.[parameter] (db-connection)
The session identifier.[parameter] (awful-apps)
The list of awful applications, as given to the awful server when invoked from the command line.
List of procedures
Miscelaneous[procedure] (++ string1 string2 ... stringn)
A shortcut to string-append.[procedure] (concat args #!optional (sep ""))
Convert args to string and intersperse the resulting strings with sep.
Debugging[procedure] (debug . args)
Print args, concatenated, to the file debug-file.[procedure] (debug-pp arg)
Pretty-print arg to the file debug-file.
Sessions and authentication[procedure] ($session var #!optional default)
Return the value of var in the session (or default if var does not exist or is #f).[procedure] ($session-set! var #!optional val)
If var is a quoted symbol, set the value of var to val. If val is not provided, var will have its value set to #f.
var can be an alist mapping session variable names to their corresponding values.
($session-set! 'foo "foo value") ($session-set! '((foo . "foo value") (bar . "bar value") (baz . "baz value")))[procedure] (link url text . rest)
Return a session-aware HTML code for a link, using the <a> procedure from html-tags.
The rest arguments are the same as the ones for the <a> procedure from html-tags, plus the following:
- a boolean. If #t, forces link to ignore the session even when enable-session is #t.
- an alist mapping variable names to their corresponding values, to be passed to uri-common's form-urlencode procedure.
- the value to the separator keyword argument to be passed to to uri-common's form-urlencode procedure.
When enable-session is #t, link automatically encodes the session identifier in the URI (unless no-session is #t).[procedure] (form contents . rest)
Return a session-aware HTML code for a form, using the <form> procedure from html-tags.
The rest arguments are the same as the ones for the <form> procedure from html-tags, plus no-session, a boolean. If no-session is #t, it forces form to ignore the session even when enable-session is #t.
When enable-session is #t, form automatically generates a hidden input field to pass the session identifier (unless no-session is #t).[procedure] (define-login-trampoline path #!key vhost-root-path hook)
Define a trampoline -- an intermediate page accessed when redirecting from the login page to the main page.[procedure] (login-form #!key (user-label "User: ") (password-label "Password: ") (submit-label "Submit") (refill-user #t))
Return a user/password login form (e.g., for using in authentication pages).
When the refill-user is #t, the User field is reffiled with the value from the user query string value when either the session or the password is invalid.
The user-label, password-label and submit-label keyword parameters are labels to be used for the user, password and submit form widgets, respectively.
Request variables and values[procedure] ($ var #!optional default/converter)
Return the HTTP request value for the given variable var. The variable is looked for in both the query string (GET method) and request body (e.g., POST method). See the documentation for the procedure returned by spiffy-request-vars's request-vars for further details.
Database access[procedure] ($db q #!key default values)
Execute the given query (q) on the database and return the result as a list of lists or default if the result set is empty.
The values keyword parameter (a list) is a list of values to replace the placehoders in the query.
($db "insert into foo (bar, baz) values (?, ?)" values: '("bar-val" "baz-val"))[procedure] ($db-row-obj q)
Execute the given query q on the database and return an one-argument procedure which takes as argument the name of the database field to get the value.
(let ((& ($db-row-obj "select full_name, phone from users where user_id=1"))) (<p> "Full name: " (& 'full_name)) (<p> "Phone: " (& 'phone)))
Warning: currently $db-row-obj is only implemented for Postgresql databases.[procedure] (sql-quote . data)
Escape and quote the concatenation of data to be used in SQL queries.
Warning: for Sqlite databases, sql-quote just replaces ' by '' and quotes the data. For Postgresql, sql-quote quotes the result of escape-string.
Define an awful page.
path is the path to the page. It can be represented by two types: a string and a regular expression object. If it is a string, the path used in the URI will be bound to the given no-argument procedure contents. If it is a regular expression object, any request whose URL path matches the regular expression will br handled by the one-argument procedure contents. This procedure will be given the requested path.
The css, title, doctype, headers and charset keyword parameters have the same meaning as http://wiki.call-cc.org/eggref/4/html-utils's html-page.
If no-ajax is #t, it means that the page won't use ajax.
If no-template is #t, it means that no page template (see the page-template parameter) should be used.
If no-session is #t, it means that the page should not use session.
If no-db is #t, it means that the page should not use the database, even when database usage is activated by enable-db and db-credentials is not #f.
(use srfi-1 ;; for filter-map regex) ;; for regexp ;; http://host:port/foo => "bar" (define-page "/foo" (lambda () "bar")) ;; http://host:port/add/1/2/3 => 6 (define-page (regexp "/add/.*") (lambda (path) (let ((numbers (filter-map string->number (string-split path "/")))) (number->string (apply + numbers)))))[procedure] (define-session-page path contents . rest)
Define a session-aware page. When the page is accessed and a correponding session does not exist, it is created. If the session already exists and is not valid, it is recreated. If the session already exists and is valid, then it is refreshed.
The rest parameters are the same as for define-page.
Here's an example (the arc challenge):
(use awful html-utils spiffy-request-vars) (define-session-page "said" (lambda () (with-request-vars* $ (said) (cond (said ($session-set! 'said said) (link "said" "click here")) (($session 'said) => (lambda (said) (++ "You said: " said))) (else (form (++ (text-input 'said) (submit-input)) action: "said" method: 'post))))))
Ajax[procedure] (ajax path id event proc #!key target (action 'html) (method 'POST) (arguments '()) success no-session no-db vhost-root-path live prelude update-targets)
path is the URL path (a string) of the server side handler. This path is placed under the (app-root-path)/(ajax-namespace) path. So, if your app-root-path is "my-app", your ajax-namespace is "ajax" and you use "do-something" as the first argument to ajax, the URL for the server side handler would be "/my-app/ajax/do-something".
id (a quoted symbol) is the id of the DOM element to be observed.
event (a quoted symbol or a list) is the event(s) to be observed. If it is a quoted symbol (e.g., 'click), only this event will be bound. If event is a list of events, all the events from the list will be bound.
proc is a no-argument procedure to be executed on the server side.
The target keyword parameter is the id of the DOM element to be affected by the result of proc.
The method (a quoted symbol, usually 'GET or 'POST) keyword parameter is the HTTP method to be used by the ajax request.
The arguments keyword parameter is an alist mapping request variables (symbols) to their values (strings). ajax uses these arguments to assembly the query string or the request body to send to the server when performing the ajax request.
arguments: '((var1 . "$('#var1').val()") (var2 . "$('#var2').val()"))
If the no-session keyword parameter is #t, it means that no session should be considered (ajax implicit sends the session identifier when no-session is #f).
If the no-db keyword parameter is #t, it means that the should be no attempt to connect the database, even when database usage is activated by enable-db and db-credentials is not #f.
The vhost-root-path keyword parameter (a string) is the vhost root path. It is useful for explicitly separate pages defined using the same path (see define-page) but for different vhosts.
The live keyword parameter (boolean) indicates wheter ajax should use JQuery's live method (see http://api.jquery.com/live/).
The update-targets keyword parameter a boolean indicating whether multiple targets should be updated upon ajax response. When update-targets is used, the procedure proc used as argument to ajax should yield an alist as result. The alist maps DOM elements identifiers to their corresponding values.
Here's an example:
#!/usr/bin/awful (use awful html-tags) (enable-ajax #t) (define-page (main-page-path) (lambda () (ajax "foo" 'foo 'click (lambda () '((a . 1) (b . 2) (c . 3))) update-targets: #t) (<div> (link "#" "foo" id: "foo") (<div> id: "a") (<div> id: "b") (<div> id: "c"))))
#!/usr/bin/awful (use awful html-tags) (enable-ajax #t) (define-page (main-page-path) (lambda () (ajax "foo" 'foo "click" (lambda () "hey") success: "$('#bar').html(response + ' you!')") (++ (link "#" "foo" id: "foo") (<div> id: "bar"))))
The ajax procedure is session, HTTP request and database -aware.[procedure] (periodical-ajax path interval proc #!key target (action 'html) (method 'POST) (arguments '()) success no-session no-db vhost-root-path live prelude update-targets)
Periodically execute proc on the server side, using (app-root-path)/(ajax-namespace)/path as the URL path for the server side handler.
interval (a number) is the interval between consecutive executions of proc, in milliseconds.
The meaning of the keyword parameters is the same as for ajax's.[procedure] (ajax-link path id text proc #!key target (action 'html) (method 'POST) (arguments '()) success no-session no-db (event 'click) vhost-root-path live class hreflang type rel rev charset coords shape accesskey tabindex a-target prelude update-targets)
A shortcut to
(begin (ajax path id 'click proc ...) (<a> href: "#" [...other <a> keyword parameters...] id: id text))
The meaning of the target, action, method, arguments, success, no-session, no-db, event, vhost-root-path, update-targets and live keyword parameters is the same as for ajax's.
The meaning of the class, hreflang, type, rel, rev, charset, coords, shape, accesskey, tabindex and a-target are the same as for http://wiki.call-cc.org/eggref/4/html-tags' <a> procedure (except that a-target is <a>'s target, since ajax uses the target keyword parameter).
The event keyword parameter syntax is the same for ajax's event mandatory parameter.
Redirection[procedure] (redirect-to uri)
Perform an HTTP redirection (code 302) to the given uri (either a string or a uri-common uri object). To be used from define-page contents. Example:
(use awful) ;; / -> /foo (define-page "/" (lambda () (redirect-to "/foo"))) (define-page "/foo" (lambda () "foo"))
The example above shows a redirection from / to /foo. Redirections can also be performed when the origin path is a regular expression:
(use awful) ;; /bar.* -> /foo (define-page (regexp "/bar.*") (lambda (_) (redirect-to "/foo"))) (define-page "/foo" (lambda () "foo"))
Web REPL[procedure] (enable-web-repl path #!key css title)
Enable the web REPL. path is the URL path to the web REPL.
The keyword parameter css is the CSS to be used the the web REPL page (see html-utils's html-page documentation for the css keyword parameter.)
The keyword parameter title (a string) is the title for the web REPL page (see html-utils's html-page documentation for the title keyword parameter.)
enable-web-repl automatically enables ajax (see enable-ajax.)
Session inspector[procedure] (enable-session-inspector path #!key css title)
Enable the session inspector. path is the URL path to the session inspector.
The keyword parameter css is the CSS to be used the the session inspector page (see html-utils's html-page documentation for the css keyword parameter.)
The keyword parameter title (a string) is the title for the session inspector page (see html-utils's html-page documentation for the title keyword parameter.)
enable-session-inspector automatically enables session (see enable-session.)
Tips and tricks
Use link and form for links and forms
Instead of using <a> and <form> for creating links and forms, respectively, consider using the link and form procedures instead. They are specially useful when using sessions, since they transparently handle the session identifier for you. Even if you don't use sessions, they may save you some work if one day you decide to use sessions (then you won't have do to anything regarding to links and forms).
Use with-request-vars when referencing the same request variable multiple times
When you need to access the same request variable more than once, consider using spiffy-request-vars' with-request-vars.
For example, instead of:
(use awful) (define-page "save-and-show-user" (lambda () ($session-set! 'user ($ 'user)) (++ "Welcome " ($ 'user) "!")))
consider using something like:
(use awful spiffy-request-vars) (define-page "save-and-show-user" (lambda () (with-request-vars $ (user) ($session-set! 'user user) (++ "Welcome " user))))
Use the web REPL and the session inspector for debugging
Here's a simple recipe to allow access for your local machine to the web REPL (/repl) and to the session inspector (/session-inspector).
(session-inspector-access-control (lambda () (member (remote-address) '("127.0.0.1")))) (enable-session-inspector "/session-inspector") (web-repl-access-control (lambda () (member (remote-address) '("127.0.0.1")))) (enable-web-repl "/repl")
To access them, just point your browser to http://localhost:<port>/repl and http://localhost:<port>/session-inspector, respectively.
Create custom page definers when page-template and/or plain define-page are not enough
You can define your own page definers when page-template or the plain define-page is not enough for what you need. Here's an example:
#!/usr/bin/awful (use awful html-tags) (define (define-custom-page path contents) (define-page path (lambda () (<html> (<body> (contents)))) no-template: #t)) (define-custom-page (main-page-path) (lambda () "Hey!"))
If you access http://localhost:8080 you'll get the following HTML code:
Debugging: error messages on the browser window
Error messages right on the browser window can be quite handy for debugging (although not for production environments). Here's a way to accomplish that:
(use awful html-tags) (page-exception-message (lambda (exn) (<pre> convert-to-entities?: #t (with-output-to-string (lambda () (print-call-chain) (print-error-message exn))))))
Run awful without arguments to quickly share a file
When invoked without arguments, awful (the application) starts the web server using the current directory as root path and keeps listening on port 8080. So, if you want to quickly share a file (or some files), change to the directory containing the files and execute awful. The access http://<host>:8080/<the-file-you-want>.
Here's a suggestion:
Awful doesn't mean anything special. It's just awful. But folks on freenode's #chicken (IRC) have suggested some acronym expansions:
- A Whole Freaking Universe of Lambdas
- Authored Without Full Understanding of Logic
- Another Web Framework Understating Logic
- All Worthless Frameworks Unchain Laziness
- Armed With Flimsy Utility Lisp
- Awful Will Fed Up Lispers
- Awful Wildly Finalizes Unfinished Loops
- Another Widely Foolish Unknown Language
- Ain't Work For Unpleasant Laywers
- Aliens Would Find it Utterly Lame
- Aren't We Funny and Uncaring Lurkers
- Awful Will F*** Up Logic
- Awful Will Fart Upon Leaving
- Again Web Frameworks Underscore Lameness
- Attention While Fully Utilizing Laziness
- Another Webserver F***ing Up the Line
- Again We Forget Unclosed Lambdas
- All Web Features Understood Losslessly
- Anything With Fully Universal Lambdas
- Again We Fail Under Load
- Apocalyptic Warthogs Find Undiscovered Lands
- Accessible Web Framework Using Lisp
- Another Weird Framework Using Lisp
- All Waffles Fear Unicorn Landings
- A Working Facility Underscoring Laziness
- Another Webbot Flapping Under Lines
- Anybody Will Fake Unrealistic Loveletters
- Armadillos Would First Use Legs
- Astonishing Whales Fill Up Lakes
- Alternative Way to F*** Up Lisp
- Another Way to Find Undressed Ladies
- Amazing! Wonderful! Fantastic! Unbelievable! Lame.
- All Wonders Feel Useless Later
- Amazingly Wonderful Feeling, Using Lambdas
- Alligators Will Fear Us, Lunatics
- All Wussies Fear Ultimate Lambda
- Animals Will Find Us Letal
- Advanced Web Framework: Ultimate Lucubration
- Awful Will Feed Urban Lethargy
- Argument With Focus Upon Labelling
- Another Word Faking Unpremeditated Label
- Again We Find it Utterly Useless
- Ain't Work For Unattractive Ladies
- A Way For Using Lambdas
- Awful Way For Using Lambdas
- Apparently We Freaks Understand Lambdas
- Again We Foolishly Use Lists
- At Work Fools Use Lisp
- Again We Foolishly Use Lisp
- Another Wimp Fall Upon Lisp
- Accepting Whatever Fools Undertake Lightly
- Absurd Word For Unnatural Lingo
- Alternative Word For Useless Loser
- Acronym With Filled Up Letters
- Acronym We Find Utterly Lame
- Another Webserver Functioning Until Launched
- Applications With Familiar, Understandable Language
- (Awful Words Forming Useless List)
- All Who Force Unusual Layout
- Again We Fear Undescribable Lamenting
- Awful Will Favour Ugly Layouts
- Apes With Frequent Uncontrolled Lunacy
Acknowledgements (IRC nicknames on freenode's #chicken):
FAQ (aka Fakely Asked Questions)
How does awful bind URIs to files/directories on the filesystem and procedures?
Explanation by example:
When a procedure is bound to a path a (like (define-page "a" (lambda ...))) and the request is made to a/, the awful behavior is the following:
- if the path a does not exist, the server replies with the result of the evaluation of the procedure bound to a.
- if a is an existent file, the server replies with the file contents (or the result of processing the file, in case it is a special one like web-scheme or .ssp files)
- if a is an existing directory and the directory contains one of the files in index-file (see documentation for Spiffy), with the index file contents (or the result of processing the file, in case it is a special one like web-scheme's or .ssp)
- if a is an existing directory and does not contain index files, the server replies with the result of the evaluation of the procedure bound to a.
Known bugs and limitations
- The ajax procedure is not session-aware when called from define-session-page (it is when called from define-page) and cookies are not enabled (i.e., enable-session-cookie is #f). To workaround this bug, you can explicitly pass the sid (see the read-only sid parameter) along with requests (see the arguments keyword parameter for ajax and ajax-related procedures).
- Awful currently doesn't detect if cookies are enabled on the client side.
- initial support for storing session identifiers in cookies. Added the enable-session-cookie and session-cookie-name parameters. By default, cookies-sessions are enabled (relevant only when using sessions, of course). Warning: if your code relies on the availability of session identifiers in the query string or in the request body for whatever reason, it will break.
- initial support for HTTP redirection via redirect-to.
- web-repl and session-inspector beautification
- web-repl and session inspector provide a headers keyword parameter
- fixes for define-page regarding to regex-based paths
- ajax always prioritizes the success keyword parameter value (when it's given) to generate code for JQuery's success attribute, even when target and update-targets are given
- awful (the application) handles the -v and --version command line options
- added the update-targets keyword parameter for ajax, periodical-ajax and ajax-link procedures (multiple targets update support)
- added the debug-resources parameter for debugging the resources table
- the js keyword parameter for ajax, periodical-ajax and ajax-link has been renamed to success (to match JQuery's API naming for Ajax). Warning: if your code uses the js keyword parameter for ajax, periodical-ajax or ajax-link, this change will break your code.
- the default value for main-page-path is now "/" (it was "/main" before). There's no more main page redirection. Warning: if your code relies on hardcoded "/main" paths (instead of using (main-page-path)) or if you rely on the main page automatic redirection, this change will break your code.
- ajax bugfix regarding to session identifiers (could cause "Invalid session ID" errors in some cases, specially with define-session-page).
- better handling of URI paths regarding to paths as directories (see FAQ's How does awful bind URIs to files/directories on the filesystem and procedures?)
- bug fix: fixed unintended shadowing of js in ajax
- added the prelude keyword parameter for ajax, periodical-ajax and ajax-link
- $db supports the values keyword parameter (patch by Stephen Eilert)
- awful (the application) can now be invoked without arguments
- awful (the application) handles -h and --help
- dropped jsmin requirement
- ajax and main page redirection issues fixes
- page-access-control controls access to pages even when no session is in use
- Warning: the following parameters have been removed: enable-reload, reload-path and reload-message. Now the way to define reloaders is via define-page.
- new parameter: awful-apps (a list of awful applications as passed to the awful server)
- bug fix for (reload-path) handler
- support for regex-based page paths (see define-page)
- define-page checks whether the second arg is a procedure.
- use -O3 -lambda-lift instead of -O2 for compilation
- main-page-path redirection made with code stolen from spiffy's send-response
- .meta bug fix. postgresql is not required as a dependency (thanks to Stephen Pedrosa Eilert for pointing this issue).
- added define-session-page
- bug fix for link
- jquery updated to 1.4.2 (ajax-library).
- link's args keyword parameter renamed to arguments (the same as ajax's).
- Session-aware link and form procedures. Bug fix for ajax-link (was not passing the class keyword argument to <a>).
- Containers for user and password fields (login-form)
- awful sets Spiffy's root-path to (current-directory)
- Multiple database support. Currently Postgresql (via postgresql egg) and Sqlite3 (via sqlite3 and sql-de-lite eggs) are supported. See awful-postgresql, sqlite3 and sql-de-lite eggs.
- Removed requirement for postgresql
- enable-db is now a procedure (not a parameter as before) and accepts no arguments
- login-form gets the user refilled when the session or passowrd is not valid. This feature may be disabled by setting the refill-user keyword parameter for login-form to #f (default is #t). Thanks to Arthur Maciel for suggesting this feature.
- jquery updated to 1.4.1 (ajax-library).
- support for jquery's live method (live keyword parameter for ajax, ajax-link and periodical-ajax). See http://api.jquery.com/live/
- bug fixes: periodical-ajax and ajax-link propagate vhost-root-path to ajax
- added awful-start and dropped spiffy requirement for awful (the server)
- dropped requirement for miscmacros
- added more ajax tests
- ajax-link accepts all <a>'s keyword arguments
- Explicitly depends on http-session 2.0
- Reload handler register the reload path after reloading.
- disable-reload? renamed to enable-reload. enable-reload is #f by default.
- awful (the server) allows applications to use Chicken syntax (use, include etc)
- Initial release