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.

9 Comments:

Blogger Unknown said...

Neat stuff..

Is there any scheme that compiles down to x86 or some fast implementation, that supports the PLT libraries, AND lives inside Apache like PHP (a mod_pltscheme)?

That would be a great combination.

08:39  
Blogger Jens Axel Søgaard said...

Besides PLT Scheme none comes to mind.

Running a PLT web-server behind Apache is widely used - even scheme.dk uses this technique.

19:10  
Blogger Hazooma said...

Thanks for a great post. Can you point to a walk-through for running PLT web-server behind Apache?

17:55  
Blogger Jens Axel Søgaard said...

Hi Hazooma,

See this mail from the PLT Scheme mailing list:

PLT web-server and Apache

If you run into problems, just mail the PLT Scheme mailing list. There's quite a few people that use the method.

21:02  
Blogger Hazooma said...

Hi Jens,
thanks for the response. However, I feel it is very uncomfortable having to type all the scheme code to generate the HTML files and of course editors are out of the questions. Is there a way to be able to write script that embeds scheme code when necessary but leave all the HTML as it is. Like in ruby for example, you write rhtml files that are basically html files but have some ruby code embedded, same as jsp, etc.

21:22  
Blogger Jens Axel Søgaard said...

Hi Hazooma,

Yes. One way is to use the "preprocessor" collection. If you are unsure on how to set things up, then send a mail the PLT Scheme mailing list.

21:27  
Blogger Joshua Herman said...

This doesn't seem to work with PLT scheme 4.0 for some reason

01:12  
Blogger yc said...

Hi Jens -

have you tried to fully integrate the PRG idiom with send/suspend etc?

It seems that the two approach are mutually incompatible, but would be great to get your take on how it can be done.

TIA,

22:38  
Anonymous Anonymous said...

i love all the series of your posts; the Control & View potion in Web development is to tell the developer to make their application very precise with quality assurance of web 2.0 development company industries

08:38  

Post a Comment

<< Home