Printable manual:
brl.pdf (340Kb) or
brl.dvi (204Kb)

4. Learning BRL by Example


4.1 Web Page for Bruce

The example below, `bruce.brl', illustrates the basics of BRL.

 
[(define myname "Bruce")
 (define my "Bruce's")]

<pre>
This is [my] web page.
[myname myname
 myname myname]

[my] favorite person is [[your name here].
[my] favorite number is [(brl-random 2)].
</pre>

When you visit this page, the web server uses BRL to transform this page into regular HTML to be sent back to your browser. Your browser does not need to know anything about BRL to view this page. Here is the output sent to your browser:

 
<pre>
This is Bruce's web page.
BruceBruceBruceBruce

Bruce's favorite person is [your name here].
Bruce's favorite number is 0.
</pre>

As you can see, the define expressions do not result in any output. They merely define variables (myname and my) for later output. Variable substitution transforms "This is [my] web page" into "This is Bruce's web page."

The "BruceBruceBruceBruce" output shows that spaces and line breaks do nothing inside square brackets. Values of multiple expressions are output with no space in between. Outside of square brackets, spaces and line breaks are sent verbatim.

An important note on the "[my] favorite person is [[your name here]" line is that outside square brackets there is only one special character, the open square bracket. If you want an open square bracket to appear in the output, it needs to be "escaped" by putting two. The close square bracket is not special and does not need to be escaped.

The (brl-random 2) expression outputs 0 or 1, so different visitors will see different results.

Advocacy: Syntax for variable substitution is extremely intuitive. Square brackets are used just as in English, i.e. text outside the brackets is a literal quotation; text inside the brackets is not.

The first thing that speeds BRL programming is that square brackets are easier to type than the combination "angle-bracket plus something else" syntax used in ASP, JSP and PHP. Such syntax is billed as more standards-compliant, but generally isn't. It's often used in entity attributes, e.g. <img src="<%=myurl%>">, where angle brackets are illegal. Other illegal angle brackets, e.g. the less-than operator, show up in embedded code. Even if that syntax is used in such a way as to produce a standards-compliant source file, there is no guarantee that standards-compliant output will be produced.

Static HTML content tends to have a lot of angle brackets. Delimiting dynamic content with angle brackets makes it difficult to visually pick out. BRL's square-bracket syntax makes it easy, without the need for a syntax-highlighting editor.

Non-literal expressions other than variable substitution are quickly spotted by the opening paren, e.g. [(brl-random 2)] is quickly recognized as more than variable substitution. A more common syntax for programming languages would be [brl-random(2)], which would take slightly longer to recognize as more than variable substitution. Small improvements in readability add up to a more beautiful language.


4.1.1 Understanding BRL Inside-Out

This next example will go one step farther with the (define myname "Bruce") expression in Web Page for Bruce.

 
[(define myface ]
<img src="/img/myface.jpg" alt="Bruce's face">
[)]

Prior to now, it looked as if BRL was just Scheme expressions enclosed in square brackets. This example breaks that mental model. It looks like a define expression that starts within one set of square brackets ends within another set. However, there's another way of looking at it.

BRL's use of square brackets is the opposite of what you might think. It isn't that [ marks the start of Scheme code and ] marks the end. Instead, ] marks the start of a string and [ marks the end of a string. For example, (define myname "Bruce") could also be written as (define myname ]Bruce[). The example above differs only in the amount of text between ] and [.

Additionally, strings can be delimited by the beginning and end of a BRL file.

The best way to think of BRL is as a Scheme interpreter(2) with an alternative syntax for strings. Similar to when you type "Hello World" to a Scheme interpreter and it echoes back the string you typed, BRL reads from the beginning of a file to the first non-escaped [ and outputs the resulting string.

In Web Page for Bruce, BRL reads a string starting with the beginning of the file and ending with [. The next thing BRL reads is a compound expression consisting of the three sub-expressions define, myname, and "Bruce". This doesn't produce any output. Then it reads another define expression. Go back and read that example again, this time looking from an inside-out perspective. Play the role of a BRL interpreter. Read a Scheme expression. Output its value. Repeat. BRL is extremely simple when you know how to look at it.

Advocacy: Other languages can make only very limited use of the text "outside" code delimiters, because it must be converted to print statements. These can be enclosed in loops or other flow-control constructs, but can't be used as function arguments.

As a result, programmers frequently use quote-and-backslash syntax for strings. Omitted backslashes are a common reason for code not working right the first time. Important spaces are often omitted because it's counterintuitive to put them between a quote mark and the quote-enclosed text, e.g. " favorite.... Using BRL speeds development by eliminating these annoying sources of error. With other languages, the best you can hope for is that yet another string-quoting syntax is available and better than either the template syntax or quote-and-backslash.

Other languages are designed by piling feature upon feature. BRL is designed by removing the restrictions on existing features that make additional features appear necessary. There is tremendous power in being able to use BRL's template syntax in any context, not just "top-level" output to the web browser, as subsequent examples will illustrate.


4.1.2 Bruce's Ruse Gets Trickier

Bruce's Web Page is already playing games with visitors. His "favorite number" is either 0 or 1, chosen randomly. Let's take this one step further, adding more sophisticated random content.

 
[(define myname "Bruce")
 (define my "Bruce's")]

<pre>
This is [my] web page.

[(if (zero? (brl-random 2))
     (brl myname] refers to [myname] in 3rd person.[)
     (brl ]Should [myname] run for office?  Yes![))]
</pre>

In this example, (brl-random 2) returns either 0 or 1. If it returns 0, visitors will see "Bruce refers to Bruce in 3rd person." Otherwise they will see "Should Bruce run for office? Yes!"

Let's use the inside-out perspective to clarify what's happening. We have an if expression, which has 3 sub-expressions. The first sub-expression is a predicate, in this case, (zero? (brl-random 2)). If the predicate evaluates to true, the second sub-expression is evaluated. If false, the third is evaluated.

The second sub-expression is a brl expression. It takes all its sub-expressions and outputs them as part of the web page. Using the inside-out perspective, we see that its sub-expressions are the variable myname, the string " refers to ", the variable myname again, and the string " in 3rd person.".

As a performance optimization, brl expressions print directly to the web page rather than returning a string to be output. When you need such a string, use brl-string instead of brl. For example:

 
[(define myface 
   (brl-string ]<img src="/img/myface.jpg" alt="[my] face">[)]

You can also use the standard Scheme string-append in cases like the above, when no non-strings are involved.

Advocacy: HTML coders who know nothing about programming can quickly learn to deal with BRL. Everything outside square brackets is their domain; everything inside is off limits. In the define myface example above, width/height attributes could be added. Attributes with quotes could be inserted without backslashes. The HTML coder needn't know five different syntactic conventions for strings, just one. An adventurous HTML coder might even replace alt="[my] face" with alt="[myname]" and find that it works! BRL truly provides gentle-slope learning for the non-programmer.

Traditional tools require extreme caution by HTML coders working "within" programming code. New tools continue to be written in an attempt to make walls that keep non-programmers away from programming code. However, there will always be a point where programming and non-programming meet. BRL is the best tool for that meeting point.


4.2 Shared variables and common headers/footers

Any definition from "Web Page for Bruce", e.g. (define myname "Bruce"), can be cut from that page and put into your `sitedefs.scm' file. Then any instance of [myname] on any page will output Bruce. If you don't know where `sitedefs.scm' is, put the following code in a BRL page:

 
Look in [brl-scmdir] for sitedefs.scm

That page will tell you where to look.

Function definitions go in `sitedefs.scm' as well. A convenient way to do BRL development is to create functions in the file you're working on as needed, then later cut/paste useful functions into `sitedefs.scm' for use in other pages.

Common headers/footers can be implemented by defining strings in sitedefs.scm, but there is a better way. To output a common header in a BRL page, use the following code:

 
[(paste "/header.brl")]

This will insert output from `header.brl' (found in the document root, or under brldir, depending how BRL is configured at your site). This will not introduce variables from `header.brl' into your page. Use `sitedefs.scm' for that.

In the above example, "header.brl" and "../header.brl" would work just as they would in an HTML anchor. Use brl-url-contents instead of paste if you want to paste in output from another server.


4.3 HTML Forms and CGI Environment Variables

 
 1 [
 2  (define-input word)
 3 ]
 4 <html>
 5 <head>
 6 <title>Your word: [word]</title>
 7 </head>
 8 
 9 <body>
10 <p>Your word is [word].</p>
11 <form>
11 Type a word: <input name="word">
13 <input type="Submit">
14 </form>
15 </body>
16 </html>

In this example, the define-input syntax on line 2 is used to name a variable that will hold the value of an HTML input of the same name. If there had been more than one such input, they would all be included in the same line, e.g. (define-input word1 word2).

If you try out this example, you'll get a form into which you can enter a word. When you submit the form, you'll go to the same page, with the word you typed substituted for [word].

Interestingly, when you first try this example, before you submit any form, you'll see () substituted for [word]. This is because no form input named word was submitted. Form inputs can be submitted with any number of values, e.g. same-name checkboxes. Usually you expect one value, in which case you'll get a string. However, if there are zero, two or more values, you'll get a list.

Here's something important to remember about HTML forms: You never know what value you're going to get, or if you're going to get any input at all. Validate your inputs if you plan on doing anything interesting with them.

Below is an example of validating inputs. It's a rather involved example. The source for it is in `learnbrl.war'. It will pay to try out this example live as you learn the concepts in it.

 
 1  [
 2   (define (valid w)
 3     (if (string? w)
 4         w
 5         "something"))
 6   (define-input (valid word1) (brl-nonblank? word2) word3)
 7   (define-cgi SERVER_NAME)
 8  ]
 9  <html>
10  <head>
11  <title>Backwards</title>
12  </head>
13  
14  <body>
15  <form>
16  Type three words: <input name="word1">
17  <input name="word2"> <input name="word3">
18  <input type="Submit">
19  </form>
20  <p>[word1] spelled backwards is
21     [(list->string (reverse (string->list word1)))]
22  </p>
23  [(brl-when word2 ]
24   <p>Second word: [word2]</p>
25  [)]
26  <p>Third word: [word3]</p> 
27  <p>This message brought to you by [SERVER_NAME] as a public
28  service.</p>
29   [(brl-guess-input brl-context)]
30  </body>
31  </html>

In this example, valid is a procedure defined in lines 2-5 to validate inputs. For those who skipped the introduction to Scheme, here's what it does: It takes an argument, w. If w is a string, w is returned. If w is not a string, return the string "something".

The define-input syntax on line 6 passes the word1 input through the procedure we named valid before binding it to the variable word1. Later, line 21 can simply assume that word1 is a string. Note that if you want to use valid in other pages, you can cut it from lines 2-5 and paste it into `sitedefs.scm'.

Also on line 6, brl-nonblank? is used on word2 to return false (#f) if word2 is an empty list or a blank string. Otherwise it returns word2(3) This makes word2 useful in all kinds of conditionals, including the (brl-when ...) code on lines 23-25.

On line 6, we chose to leave word3 unvalidated, just to show that validating some inputs does not mandate that you validate all. Try changing it to validate with brl-list so that it's always a list, even if there's exactly one value. This is useful for same-name checkboxes.

Line 7 illustrates the define-cgi syntax. Though we chose not to validate the variable in this example, you can do validation just as with the define-input example. Validation is sometimes necessary because of user-supplied data. Even though BRL is not implemented using CGI, it uses this syntax to mimic CGI environment variables

On line 29, brl-guess-input returns a list that prints as a valid define-input line for all the form inputs received. This is handy if you have a form with lots of inputs. When you first write a BRL page as the action for that form, you don't have to pick out all the input names. Just put [(brl-guess-input brl-context)] on the BRL page, submit the form, and use the resulting define-input line as your starting point.

Advocacy: Competing systems have two common approaches to handling form inputs. Some, e.g. PHP3, automatically assign variables named according to form inputs. This is makes for convenient coding, but not convenient debugging. There is no way to distinguish a typo from a valid form input name that simply wasn't supplied. In large pages, this can cause difficult-to-track bugs, where an empty string is being used somewhere. Also, having form inputs automatically shadow global variables opens some serious security holes. In PHP4, the variables are no longer assigned automatically. However, you still have the typo issue.

Other systems, e.g. Java servlets, have a special Request object with methods for getting form input values. You assign a variable, e.g. word=request.getParameter("word");. This is good because it gives you only one place to make a difficult-to-catch typo. If you later reference werd instead of word, you'll get an error rather than simply having werd used as an empty string.

BRL takes the optimal middle ground. You have one place where inputs are declared. Typos later will result in errors. However, this one place uses a concise, non-redundant syntax, making it almost as convenient to code as PHP3 minus the security holes, and just as convenient to debug as a servlet.

What's more, the validation syntax encourages developers to write code that's easy to audit for security holes related to user-supplied input.


4.4 Path Segments

Suppose you want, for example, http://example.com/map/5/6 to represent points on a map with coordinates (5, 6). You'll first need to tell the BRL servlet what file to look at for such URIs. Here's what you'd put in `sitedefs.scm', assuming that `/var/www/html/map.brl' is the full path to the BRL file:

 
(define (brl-uri-map uri default-file)
  (cond
   ((brl-starts-with? "/map/" uri)
    "/var/www/html/map.brl")
   (else default-file)))

You may also need to configure your web server to recognize that such URIs should be passed to the BRL servlet. However, for testing purposes it might be easier to just use http://example.com/servlet/brlsv/map/5/6 as the URL.

Here is the code you would put in `map.brl':

 
[
 (define-path (string->number x) (string->number y))
]
<html> <head><title>Position ([x], [y])</title></head>
<body>
 You are at position ([x], [y]) on the map.
 You may go <a href="[(+ y 1)]">north</a>,
 <a href="[(- y 1)]">south</a>,
 <a href="../[(+ x 1)]/[y]">east</a>,
 <a href="../[(- x 1)]/[y]">west</a>.
</body> </html>

On line 2, define-path takes the last two path segments of /map/5/6, passes them to the string->number procedure, and binds the results to variables x and y. Had we been content to leave them as strings, we could have simply used (define-path x y).

We could also have used (brl-path-segments brl-context) to return a list of the three strings, ("map" "5" "6"). This is more useful than the define-path syntax if the number of segments varies.

We can omit line numbers in this example, because only line 2 teaches something new about BRL. The only other thing I'd point out is the HTML title in line 4. It is good to make the title as specific as possible. Most browsers have a menu associated with the Back button to enable users to go back several pages at once. A more specific title makes this menu useful.

Advocacy: Isn't [x] a lot more readable than <%=x%> would have been in this example? Particularly look at the <a href="..."> parts.


4.5 Cookies

A web server may pass a name/value pair called a "cookie" back to a web client, with the intent that the client pass that same cookie back to the server from then on. Do not use cookies to keep track of the state of what a particular client is doing. That client might have multiple windows open doing different things, or might have used the back button.

Appropriate uses of cookies would be to remember a user-supplied time zone or postal code, or keeping track of items in a shopping cart. Cookies in BRL are always strings, or the Scheme value #f if no cookie by that name is set.

The following example sets a cookie named c:

 
 1 [(define-cookie c)]
 2 <p>The cookie named c has value "[c]".</p>
 3 [(set! c (brl-random-typeable 2))]
 4 <p>The <a href="">next</a> value will be "[c]".</p>

If we were doing anything more interesting with the cookie value than displaying it, we would have used the same validation syntax as with define-input.

If the value were set! multiple times in that page, only the last value would be sent back to the client as a cookie.

See section 5.2 Web Functions, for more sophisticated ways to get/set cookies.


4.6 Session variables

Sometimes a cookie is almost the right thing, except the value is too large to be a practical cookie value, or the nature of the data makes it hard to validate. In such cases, data can be kept on the server side in what's called a session variable. A cookie is used to associate the client with this data, so all the caveats of the previous section apply. Additionally, session variables are transient, and time out after a certain period, configurable in your servlet engine. Most of the time, information should either be kept client-side or be put in a database on the server side. However, session variables are convenient and sometimes appropriate.

 
 1 [(define-session a b c)]
 2 
 3 <p>The session variables are [(list a b c)].</p>
 4 
 5 [
 6 (set! a b)
 7 (set! b c)
 8 (set! c (brl-random-typeable 2))
 9 ]
10 
11 <p>The session variables <a href="">will be</a> [(list a b c)].</p>

As with cookies, these values carry over page to page. If the value is set! multiple times, only the last becomes the new session variable value.

Unlike with cookies, session variables are not necessarily strings. They can be numbers, lists, vectors, serializable Java objects, etc. There is no validation syntax in define-session. It is not user-supplied data.

With the syntax (define-session (ses) a b c) we could have bound the variable ses to the session object, allowing more sophisticated operations, such as retrieving session variables whose names conflict with input names or other local variables. See section 5.2 Web Functions.

The most common misuse of session variables is in multi-step forms. To accumulate values from previous forms as you go along, use hidden form inputs instead. For example, [(brl-html-hidden a b c)] would expand to the following html:

 
<input type="hidden" name="a" value="a's value">
<input type="hidden" name="b" value="b's value">
<input type="hidden" name="c" value="c's value">


4.7 Sending e-mail

The definition below goes in `sitedefs.scm' to set things up to send mail via an SMTP server. If you are using BRL Cabaret or some other site that has already been set up, this definition is already there for you. You do not have to include it in an individual page; it is a site-wide definition (ergo, `sitedefs.scm').

 
(define mail
  (brl-smtp-sender
   "mail.example.com"           ; the SMTP server
   "www.example.com"            ; the web server
   "webmaster@example.com"      ; bounced mail goes here
   ))

This creates a procedure called mail that takes at least two arguments. The first argument is a list of e-mail addresses that are recipients of the message. The second and subsequent argument become a string that forms an e-mail message, i.e. Internet mail headers followed by a blank line, then the body of the message.

Obviously, you can define several such procedures with any names you like, but always include a default procedure named mail to ease portability of BRL code from site to site.

The following page shows how to use the mail procedure. It would be the ACTION page for an HTML form:

 
 1  <html>
 2  <head><title>Mail sent</title></head>
 3  
 4  <body>
 5  [(define-input email yourname quest colour)
 6  
 7   (mail (list "strangeman@example.com")
 8  ]From: [email]
 9  To: strangeman@example.com
10  Subject: questions three
11  
12  What is your name?              [yourname]
13  What is your quest?             [quest]
14  What is your favourite colour?  [colour]
15  [)]
16  
17  <p>Your e-mail has been sent.  Thank you.</p>
18  
19  </body>
20  </html>

Line 5 defines the inputs that come from the HTML form. Line 7 invokes the mail procedure with a list of one recipient. Lines 8-14 define the body of the e-mail message. It is important that there be no spaces between ] and the first header (line 8), and that there is a blank line after the headers (line 11). Line 15 has the close paren for the mail expression.

Let's look at this example from the perspective discussed earlier (see section 4.1.1 Understanding BRL Inside-Out). Don't look at square brackets as enclosing [code]. Look at them in the reverse sense, enclosing ]strings[ to see what's happening. The first argument to mail on line 7 is a list. The second argument, on line 8, is the string "From: ", followed by the variable email as the third argument. The fourth argument is a string that starts on line 8 with a newline character, spans several lines, and ends just before the variable yourname (the fifth argument, on line 12).

The mail procedure, as explained earlier, concatenates its second and subsequent arguments to create an Internet message, which it then sends via SMTP. Taking the inside-out perspective, the closing paren on line 15 makes perfect sense. The square bracket before it ends a string that is part of the e-mail message, since it's an argument to mail. The square bracket after the close paren begins a string that will be sent back to the browser because it's at the top level, i.e. not being used as an argument to anything.

Advocacy: In 1994, cgiemail was developed at the Massachusetts Institute of Technology to allow non-programmers to easily specify what e-mail messages sent from their HTML forms should look like using a simple template syntax. Lines 8-14 look like that syntax. A non-programmer could easily customize the example above. BRL is almost as simple as cgiemail, but much more powerful.

Only BRL uses the same friendly template syntax for constructing e-mail messages as is used for constructing web pages.


4.8 SQL in BRL


4.8.1 One-Time Preparation for All Pages

If you are using BRL Cabaret, the table and JDBC driver are already set up. You may skip ahead to 4.8.2 Queries in an Individual Page.

The following examples will assume you have a JDBC driver for a database in which you've created the following table:

 
create table favcolor(
    name varchar(20),
    color varchar(20))

In your `sitedefs.scm' file, register your JDBC driver with the sql-driver procedure. Then define a procedure for connecting to your database. By defining this procedure in your `sitedefs.scm' file, you avoid putting database usernames/passwords in BRL web pages. For example, if you were using the PostgreSQL driver:

 
(sql-driver "org.postgresql.Driver")
(define (db1 brl-context)
  (brl-sql-connection brl-context
                      "jdbc:postgresql://localhost/db-here"
                      "user-here"
                      "password-here"))

(define ss brl-mysql-string)

The brl-sql-connection procedure returns a connection to your database that can then be used to create SQL statements, and assures that this connection will be closed once the BRL page finishes, whether normally or by error. The brl-context variable is a structure that contains various information in the context of the current BRL page.

The purpose of (define ss brl-mysql-string) in this context is in case you switch back and forth between a driver that expects standard SQL string syntax (only single quotes need escaping) and a driver that expects non-standard string syntax, e.g. MySQL or PostgreSQL (backslashes also need escaping). Use ss wherever you would otherwise use brl-sql-string or brl-mysql-string.

If you need further help getting your driver working, consult your JDBC driver supplier or the following Usenet group: `comp.lang.java.databases'

Advocacy: Simple examples for competing systems generally put database info (driver, server, user, etc.) directly in the page. Only with BRL is it natural and easy to centralize, while still maintaining the flexibility of having many different DBs to connect to.

Unclosed database objects (connections, statements, etc) are a major headache for server-side web programming. Sometimes this happens because of an uncaught error. Sometimes a programmer simply forgets to close the object when done. BRL eliminates the problem in both cases.


4.8.2 Queries in an Individual Page

 
 1  [
 2   (define conn (db1 brl-context))
 3   (define st (brl-sql-statement brl-context conn))
 4  ]<html>
 5  <head><title>Favorite Colors</title></head>
 6  <body>
 7
 8  <ul>
 9  [(define rowcount
10     (sql-repeat st (name color) ("select * from favcolor")
11       (brl ]<li> [name] likes [color]
12  [)))]
13  </ul>
14
15  <p>Count: [rowcount]</p>
16
17  </body>
18  </html>

In line 2, the db1 procedure previously defined in `sitedefs.scm' is used to get an SQL connection that will be automatically closed later. In line 3, the brl-sql-statement procedure is used to get an object(4) that can be used repeatedly for SQL queries on that connection.

In line 10, we see BRL's sql-repeat syntax. The first argument is the SQL statement object to be used in the query. The next argument specifies the variable names to be used for the columns returned by the query, in this case name and color. The third argument is the text of the query. In this example it is just one string, but it could have been several pieces, some strings, some not, that would be concatenated.

Any remaining arguments to sql-repeat are Scheme expressions to be evaluated once for each row returned in the query. In this example, there is just one expression. In standard Scheme, the expression would be written as follows:

 
(brl "<li> " name " likes " color "
")

BRL's square-bracket syntax helps clarify what text is literal and what is evaluated. It just takes a little getting used to code like (brl ], which signifies the beginning of literal text contained within a BRL expression. This same syntax can be used for SQL queries, as will be seen in later examples.

The brl syntax is necessary because of the fundamental difference between BRL and Scheme. In Scheme, sequences of expressions generally return the value of the last expression. In BRL, all expressions in a sequence are output. The brl syntax is most commonly used in sql-repeat and if expressions.

The sql-repeat syntax returns the number of rows from the query. Since BRL pages normally show the return value of expressions, this example uses define rowcount to capture the value so that it can be output in a more appropriate place on line 15.

The three parentheses on line 12 close the brl expression, the sql-repeat expression, and the define expression, respectively.


4.8.3 Inserts, Updates and Deletes

For people to enter their own favorite colors into the database, they'll need a form. This is simply HTML; there is no BRL programming involved.

 
 1  <html>
 2  <head><title>Choose Favorite Color</title></head>
 3
 4  <body>
 5
 6  <form action="chosen.brl">
 7  Name: <input name="name"><br>
 8  Color: <input name="color"><br>
 9  <input type="submit">
10  </form>
11
12  </body>
13  </html>

The action in line 6 will work if your web server knows how to handle the `.brl' extension. If not, you'll need to use a longer URL. Here is `chosen.brl':

 
 1  <html>
 2  <head><title>Favorite Color Chosen</title></head>
 3  [(brl-referer-check brl-context)
 4   (define-input name color)
 5   (define conn (db1 brl-context))
 6   (define st (brl-sql-statement brl-context conn))
 7  ]
 8  <body>
 9
10  [(if (or (brl-blank? name)
11           (brl-blank? color))
12       "<p>Please go back and fill in your name/color.</p>"
13       (if (positive? (sql-execute-update st
14                       "update favcolor set color="
15                       (brl-sql-string color)
16                       " where name="
17                       (brl-sql-string name)))
18           "<p>Your favorite color has been updated.</p>"
19           (begin
20             (sql-execute-update st
21                   ]insert favcolor(name, color)
22  values([(brl-sql-string name)], [(brl-sql-string color)])[)
23             "<p>Welcome to the favorite color database!</p>"
24           )
25       )
26  )]
27
28  </body>
29  </html>

You've seen lines like 1-8 before, except for line 3. If it weren't for the brl-referer-check expression ("referrer" misspelled for historical reasons), some joker could create a form on his own web site that looks like it's doing one thing, but then when a user hits the Submit button it would change the user's favorite color in your database. The brl-referer-check expression restricts referers to your own web site. If this is not restrictive enough, you can write your own code to check the value of (cgi HTTP_REFERER). A simple brl-referer-check will eliminate most mischief, but if your site includes URLs submitted from outside sources, or if some of your privileged users have setups that do not send referer info, you should include additional checks in your code.

Here is a translations of lines 10-23 into English: If either input has not been filled in (10-11), return a message asking the user to do so (12). Otherwise, try executing an update for the user (13-17). If that update affects a positive number of rows (13), return an update message (19). Otherwise insert the appropriate data (19-22) and return a welcome message (23).

The sql-execute-update procedure takes two arguments or more. The first is the statement object to use. The second and subsequent arguments become a string representing an SQL insert, update or delete. The return value is the number of rows affected.

Starting on line 14, the standard Scheme syntax is used for strings. The alternate BRL syntax is used starting on line 21, using square brackets to mark the dividing lines between Scheme code and literal strings. Use the standard Scheme syntax for short, simple strings. Use the BRL syntax for longer strings or strings containing lots of double quotes or parentheses, or anywhere that you think someone reading your code would have trouble telling whether a double quote is starting or ending a literal string.

The brl-sql-string procedure on lines 15, 17 and 22 takes one argument. If this argument is null, "NULL" is returned. Otherwise a string is returned enclosed in single quotes, with any internal single quotes doubled, as required by SQL. This is important. A knowledgeable intruder might otherwise supply form input that executes arbitrary SQL statements in your database. If you are using or might start using MySQL, which has has non-standard string-escaping rules, see 4.8.1 One-Time Preparation for All Pages.

The final bit of syntax that deserves explanation is in lines 19-24. Use begin to group expressions so that only the value of the last expression is returned. If you want all the values to be output, group the expressions with brl instead. In this example, we don't want to see the number of rows affected by the insert; that number should always be 1.


4.8.4 Grouping Results

SQL results do not always map neatly into the result you want to see on an HTML page. Sometimes you want subtotals for numeric data or sectional divisions for other data. Like other report systems, BRL provides a mechanism for grouping SQL results. For example, suppose you had the following results:

 
color        name
------       -----
blue         Jane
blue         Joe
blue         Lancelot
red          Bill
red          Yuri

Suppose you wanted output that looked like this:

Here is the BRL code that lets you do it:

 
 1  [
 2   (define conn (db1 brl-context))
 3   (define st (brl-sql-statement brl-context conn))
 4  ]<html>
 5  <head><title>Favorite Colors</title></head>
 6  <body>
 7
 8  <ul>
 9  [(define rowcount
10     (sql-repeat st (name color)
11       ("select * from favcolor
12        order by color, name")
13       (if (group-beginning? color)
14           (brl ]<li><strong>[color]</strong>: [name)
15           (brl ", " name))
16       (brl-when (group-ending? color)
17                 #\newline)))]
18  </ul>
19
20  <p>Count: [rowcount]</p>
21
22  </body>
23  </html>

Lines 1-12 aren't significantly different from earlier examples in this chapter. Lines 13-17 are where it gets interesting. The group-beginning? syntax determines if a group is beginning. In this example, color determines the beginning of the group. So as each color group begins (13), a bullet is output, along with the color and a colon, plus the first name associated with that color (14). If a group is not beginning (i.e. 2nd and subsequent names), a comma is output followed by the name (15). Then, if we are at the end of a group based on color (16), a #\newline character is output. This just makes the HTML output cleaner in case you do "View Source" from your browser.

If you want a subgroup based on a column, e.g. col2 within a group based on col1, use (group-beginning? col1) to test for the outer group and (group-beginning? (list col1 col2)) to test for the inner group. To nest groups deeper, simply add the column names to the list.

The argument to group-beginning? or group-ending? does not have to be just a column name. For example, (group-beginning? (string-ref color 0)) would group colors together by their first letter.

If you use a constant, group-beginning? will be true only at the beginning of the result set, and group-ending? will be true only at the end of the result set. I suggest using the symbol 'all for this purpose, as it makes your intent clear.


4.8.5 URL and HTML Escapes

Continuing with the previous examples, we've set up a web site that lets people put in arbitrary strings as colors. This introduces a security issue: What if someone introduces a "color" that is actually JavaScript code or some such? The code might cause other users' browsers to do something undesired. The easy way to fix this problem is to translate the characters <, >, & and " into their respective HTML escape sequences. This is done in the following example with the brl-html-escape procedure.

 
 1  [
 2   (define conn (db1 brl-context))
 3   (define st (brl-sql-statement brl-context conn))
 4  ]<html>
 5  <head><title>Choose a Color</title></head>
 6  <body>
 7
 8  <ul>
 9  [(define rowcount
10     (sql-repeat st (color)
11  ("select distinct color from favcolor
12   order by color")
13    (brl ]<li><strong>
14  <a href="p2.brl?[
15  (brl-url-args brl-blank? color)
16  ]">[(brl-html-escape color)]</a></strong>
17  [)))]</ul>
18
19  <p>Count: [rowcount]</p>
20
21  </body>
22  </html>

We see in line 16 that the color name is properly escaped before being used as the text of an anchor.

It would be a waste to use a 22-line example just to illustrate one procedure, so the brl-url-args syntax is also illustrated in line 15. The first argument to brl-url-args should generally be brl-blank?. If you want to draw a distinction between inputs that have been sent as blank strings and inputs that have not been sent at all, you might use null? instead, but this is generally a bad idea. The remaining arguments in the brl-url-args syntax should be variable names. The variable names and their values are encoded in such a way as to be included in a URL. In the example above, a URL is generated that, when followed, goes to a page `p2.brl' and color is given as an input.

The brl-url-args syntax is convenient in that it omits blank inputs, resulting in nice, compact URLs. But sometimes you may want to simply URL-escape a value. In that case, use the brl-url-escape procedure exactly as brl-html-escape is used.


4.8.6 Searches and Sortable Columns

The next example, `colors.brl', is intended to be the action of an HTML form to search the database by name, color or both. The results appear in columns which can be sorted by clicking(5) on a column header. Successive clicks on a header reverse direction. If you're looking at the list in say, reverse order by name and then click on the color header, the list will then be sorted by color, but within each color they will be sorted in reverse order by name.

 
 1 <html>
 2 <head>
 3 <!-- [
 4  (define-input color name order1 order2)
 5  (define where-clause
 6    (string-append "where 1=1"
 7      (if (brl-nonblank? name)
 8          (string-append " and name=" (ss name))
 9          "")
10      (if (brl-nonblank? color)
11          (string-append " and color=" (ss color))
12          "")))
13  (define (valid-order str)
14    (if (or (brl-blank? str)
15            (sql-order-member str (list "name" "color")))
16        str
17        ""))
18  (set! order1 (valid-order order1))
19  (set! order2 (valid-order order2))
20  (define nonblank-orders (brl-nonblanks (list order1 order2)))
21  (if (null? nonblank-orders) (set! nonblank-orders (list "name")))
22  (define order-columns (brl-string-join ", " nonblank-orders))
23  (define (sort colname)
24    (string-append
25      "colors.brl?"
26      (brl-string-join brl-url-arg-separator
27        (brl-nonblanks
28          (list
29            (brl-url-args brl-blank? name color)
30            (brl-url-arg-seq "order"
31              (sql-order-prepend colname nonblank-orders) 1))))))
32  (define conn (db1 brl-context))
33  (define st (brl-sql-statement brl-context conn))
34 ] -->
35 <title>Search Results</title>
36 </head>
37 
38 <body>
39 
40 <table>
41  <tr>
42    <th><a title="sort" href="[(sort "name")]">Name</a></th>
43    <th><a title="sort" href="[(sort "color")]">Color</a></th>
44  </tr>
45 [(define rowcount
46    (sql-repeat st (name color)
47 (]select name, color
48 from favcolor
49 [where-clause]
50 order by [order-columns)
51      (brl ] <tr>
52    <td>[(brl-html-escape name)]</td>
53    <td>[(brl-html-escape color)]</td>
54  </tr>
55 [)))]
56 </table>
57 
58 <p>Found: [rowcount] [where-clause]</p>
59 </body>
60 </html>

Normally a search query has conditions that are always part of the where clause. In this example there are none except the search conditions, but we've put 1=1 in as a placeholder (line 6). Lines 7-9 specify a name if one was typed in the appropriate input. Lines 10-12 do the same for color. If there were other search criteria, we could add them in much the same way.

The order1 and order2 inputs are URL arguments that `colors.brl' sends to itself in order to do column sorting.

Putting arbitrary strings from form input into your SQL query is a risky proposition: there's no guarantee that the order1 and order2 inputs will be what you intended. Lines 13-17 define a function that returns a valid value for either of these variables regardless of its input. This function is used in lines 18 and 19. Line 20 gets a list of all the non-blank order columns. This is the last line that would have to change if you were to add another column.

Line 21 makes the default sort be by name. Line 22 gets a comma-separated list of columns to sort by. Lines 23-31 define a function that returns a relative URL that points back to `colors.brl' with arguments for the search parameters and the sort columns. The new BRL functions introduced here are brl-url-arg-seq, which returns a string of URL parameters with sequentially-ordered names, and sql-order-prepend, which takes a column and a list, and returns a new list with that column at the front. If the column was previously at the front, it is toggled between ascending/descending order.

Lines 32-41 are old material. Lines 42 and 43 show how the sort function is used (defined in lines 23-31). Lines 47-50 show a dynamically-generated SQL statement that incorporates the search criteria and sort columns.


4.9 String Manipulation

Scheme has basic string functions which are described in R5RS and work in all Scheme implementations. The Kawa implementation of Scheme has additional string functions described in the Kawa manual. BRL provides yet more string functions, the most important of which are described here.

Any object can be converted to a string with the brl-string procedure. But there are more efficient and more powerful means of converting objects to strings if you know something about the object.


4.9.1 Numbers to Strings

Scheme (and thus BRL) includes a procedure called number->string whose only formatting capability is an optional argument specifying a number base. E.g. (number->string 255) yields "255", and (number->string 255 16) yields "ff".

For other formatting capabilities, the brl-format procedure provides a simple interface:

For more details, see your Java documentation for java.text.DecimalFormat. If BRL is later ported to a non-Java platform, brl-format will still work the same way.

If you will be converting a lot of numbers to strings of the same format, brl-decimal-formatter will be efficient and convenient.

 
> (define money (brl-decimal-formatter "$#,##0.00" "NULL"))
> (money (* 100 100))
$10,000.00
> (money 1/3)
$0.33
> (money (list))
NULL

As can be seen in the above example, brl-decimal-formatter takes two arguments, the first being a string suitable for use with brl-format, and the optional second argument being a string to use for null values. The return value of brl-decimal-formatter is a procedure that takes one argument and returns a string. If the concept of a procedure that returns a procedure seems confusing, study the example above for a minute. You'll see it's actually quite simple.


4.9.2 Dates to Strings

In addition to numbers, brl-format can also do simple date formatting, e.g. (brl-format (brl-now) "yyyy-MM-dd") might yield "2000-05-19".

For more details, see your Java documentation for java.text.SimpleDateFormat. If BRL is later ported to a non-Java platform, brl-format will still work the same way.

Your site should have a standard format for dates that is used everywhere. It is best to define such a format in your `sitedefs.scm' file using the brl-simple-date-formatter procedure.

 
(define date10 (brl-simple-date-formatter "yyyy-MM-dd" "NULL"))

You would then use the date10 procedure anywhere a date should be formatted into a string of at most 10 characters.


4.9.3 Trimming, Splitting and Joining

To trim the whitespace off of both sides of a string, use the return value of (brl-trim str), which returns a trimmed version. It does not modify the string passed to it. If you want to modify a string, do this: (set! str (brl-trim str))

Use brl-split to split a single string into a list of strings based on a separator string, e.g. (brl-split " and " "lions and tigers and bears") yields ("lions" "tigers" "bears").

To join the string representations of a number of objects into a single string with a separator, use brl-string-join. For example, (brl-string-join ", " (list "one" 'two (+ 2 1))) yields "one, two, 3".


4.10 Connection Pools and Other Java Objects

The Kawa manual describes make, invoke, and invoke-static syntax for manipulating Java objects. This section will illustrate their use within `sitedefs.scm'. You should define procedures that manipulate Java objects only in this file, then use those procedures in individual pages. This methodology will keep the individual pages clean and portable.

The following example uses DbConnectionBroker, but could easily be adapted to use another connection-pool object.

 
 1  (define testdb-pool
 2    (make <com.javaexchange.dbConnectionBroker.DbConnectionBroker>
 3      "com.internetcds.jdbc.tds.Driver"
 4      "jdbc:url-here"
 5      "user-here" "password-here" 1 5 "/tmp/testdb.log" 1))
 6
 7  (define (db1 brl-context)
 8    (let ((c (invoke testdb-pool 'getConnection)))
 9      (brl-prepend-endproc!
10       brl-context
11       (lambda () (invoke testdb-pool 'freeConnection c)))
12      c))

When the BRL servlet starts or re-reads `sitedefs.scm', a variable testdb-pool (line 1) is bound to a new object (2). In this example, an 8-argument constructor is used. Then, the db1 procedure is defined to get a connection from this pool (line 8), then prepend to the list of things to do when a BRL-page request is complete (line 9), a procedure that takes no arguments and frees the connection (line 11). The return value is the connection (line 12).

Note that no changes are required for individual pages to take advantage of the connection pool. Previously, db1 was a procedure that opened a connection and assured that it was closed later. Now it is a procedure that gets a connection from the pool and assures that it is freed later. The individual pages don't know the difference.



This document was generated by Bruce R. Lewis on 2003-6-9 using texi2html

GNU   SourceForge