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



receives a request

stores the information in the request in various parameters

evaluates its bodies, the result of the last body must be an x-expr

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 )))

( 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 )

( 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 ]))

( 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.

