Wednesday, August 15, 2007

An Introduction to Web Development with PLT Scheme - The Control

Resume

This is the fourth post in a series of posts on programming web

applications with PLT Scheme. In part one and two the model and view of a small Reddit-like application were discussed. Part three were about the basic operation of the PLT Scheme web-server. Here in part four we will finish the discussion of the controller. Finally I'll explain how to get this code from PLaneT, so you can run it on your own machine.

Don't forget to leave a comment, if you have questions or suggestions.

The Controller

The controller handles user interactions. The concrete actions in our application are:

  • Viewing the front-page
  • Up- and down-voting a link
  • Viewing the page for submitting new links
  • Submitting a new link

For each action, we'll have a function that invokes the appropriate function in the model, receives data back, and then let the view generate a web-page based on the data. The function handling the viewing of the front-page is simply:

(define (do-front-page)
(html-front-page 0 1 (page 0)))

The function page is from the model and html-front-page in the view (see earlier posts).

Implementing the controller

An user interaction is sent to the web-server in the form of a request. Given a request, the web-server determines which servlet is to handle the request, and then calls the servlet's start function.

Here we have a choice. Even though the controller conceptually is one entity, a common way to implement controllers is to have separate servlets for each action. In this tutorial however I'll implement the controller as a single servlet, and let it dispatch on the action. The url for the servlet will be:

 http://localhost/servlets/control.scm

The action will be given as a parameter. One way to send the parameter is to include it as part of the link:

 http://localhost/servlets/control.scm?action=submitnew

Another is to let action be a hidden field in a form.

The Start Function

After the receipt of a request the web-server starts the control-servlet by calling the start function with the request in
question. The web-framework provides us with a special form servlet, that allows us to to define the start function simply as:

  (require (planet "web.scm" ("soegaard" "web.plt" 2 0))
(define start
(servlet
(dispatch-on-action)))

Here the servlet form evaluates to a function that

  1. receives a request
  2. stores the information in the request in various parameters
  3. evaluates its bodies, the result of the last body must be an x-expr
  4. construct a response based upon the x-expr and the contents of the various response parameters

During development it is mighty convenient to see any errors directly
in the browser. Therefore, let's introduce the form, with-errors-to-browser:

  (define start
(servlet
(with-errors-to-browser
send/finish
dispatch-on-action))

Dispatching on actions

Any parameters sent as part of the request, are saved as bindings in current-bindings by the servlet-form in start. The dispatch code is therefore very simple:

  (define (dispatch-on-action)
(with-binding (current-bindings) (action)
(match action
["updown" (do-updown)]
["submitnew" (do-submit-new)]
["submit" (do-submit)]
[else (do-front-page)])))

If the action is missing, the front page is shown.

Submital of new links

The action "submitnew" simply shows a page with a form, where a new url and title can be entered:

  (define (do-submit-new)
(html-submit-new-page))

Hitting "submit" will POST the data to our servlet, and the action will be "submit". The controller must send the data to the model in order to add the new link. The user should return to the frontpage. One way to achieve this is as follows:

  (define (do-submit)
(with-binding (current-bindings) (url title)
(when (and url title)
(insert-entry title url 10)))
(do-front-page))

Can you spot a problem?

The problem and its solution

What happens if the user hits the refresh button in their browser? The same data will be posted again. This is often referred to as the "Double Submit problem". The solution is to redirect the browser to the frontpage. If the user now hits refresh, then they'll just get the frontpage again.

  (define (do-submit)
(with-binding (current-bindings) (url title)
(when (and url title)
(insert-entry title url 10)))
; to make sure a reload doesn't resubmit, we redirect to the front page
(current-redirect-temporarily "http://localhost/servlets/control.scm")
(html-redirect-page "Redirecting to frontpage"))

The redirection is put in place, by setting the parameter current-redirect-temporarily. When the servlet form constructs the reponse it checks whether any redirection parameters are set, if not it produces a normal "200 OK" response; however, if set, it produces a redirection response; e.g., in the case of current-redirect-temporarily, a "302 Found".

Note: You can also use current-redirect-see-other to get a "303 See Other" response.

Note: See
Redirect after Post by Michael Jouravlev for a thorough discussion of the POST-Redirect-GET technique.

Up- and Down-voting

The up- and down-arrows could be simple links, but if they were we would run into a similar problem with refresh. We therefore turn each link into a form, and handle its submital with the POST-Redirect-GET technique as before. The only complication is that big submital buttons aren't pretty; images of arrows are much prettier. This is solved with a little JavaScript (see the post on the view).

  (define (do-updown)
; an arrow was clicked
(with-binding (current-bindings) (entry_id arrowitem)
(match arrowitem
["down" (when entry_id
(decrease-score entry_id))]
["up" (when entry_id
(increase-score entry_id))]
[else
'do-nothing]))
; to make sure a reload doesn't resubmit, we redirect to the front page
(current-redirect-temporarily (format "http://localhost:~a/servlets/control.scm" port))
(html-redirect-page "Redirecting to frontpage"))

Downloading the code

To download the code and try it, open DrScheme and enter the following in the interaction window:

  (require (planet "install.scm" ("soegaard" "listit.plt" 1 0)))

Then call install to copy the web-site to a
folder of your choice:

 > (install "c:/tmp/my-listit")
Copying the web-site to c:/tmp/my-listit .
Please wait...
The web-site has now been copied to c:/tmp/my-listit .
To test it, open "launch.scm" in DrScheme and run it.

Have fun!

Leave a comment, if you have questions or suggestions.

Sunday, August 05, 2007

Number Theory

Today I released a collection of number theory functions through PLaneT.

The code began as an experiment. I grabbed a book on number theory from the shelve ("Elementary Number Theory" by Gareth A. Jones and J. Mary Jones) and began illustrating each definition and each theorem with Scheme code. The first half of the source code is thus a well commented mix of definitions, theorems and code.

The second half contains more sophisticated algorithms mostly from the excellent book "Modern Computer Algebra" by Joachim von zur Gathen and Jürgen Gerhard. The algorithms for factorizing large integers come from this book.

Finally there are some definitions of special functions, mostly inspired by the problems of the Euler Project. The revival of the code is due to the Euler Project, which is a collection of small problems of very varying difficulty. The tools I have used to solve the problems (apart from the number theory library) are David Herman's memoize package (don't forget to use memoize* when working with numbers) and the eager comprehensions from srfi 42.

The documentation of the number theory library lists all the available functions.

Please write with bugs, suggestions, and improvements.