Bluish Coder

Programming Languages, Martials Arts and Computers. The Weblog of Chris Double.


2010-12-16

Handling POST requests with Ur/Web

Ur/Web makes it easy to write web applications that have HTML forms and handle the posted data. It gets a bit trickier if you have a POST handler that does not have an HTML form in the web application but is instead called by an external service.

The following is a function that handles a POST request field containing a string. It puts that string in a database:

table messages : { Message : string }

fun notifyMessage data =
  dml (INSERT INTO messages (Message) VALUES ({[data.Message]}));
  return <xml></xml>

This notifyMessage function won't type check unless we have a corresponding function that has an HTML form that submits to it. Without an HTML form in the application that uses notifyMessage we get a compile error like the following:

post.ur:4:46-4:62: Couldn't prove field name disjointness
   Con 1:  [#Message = Basis.string]
   Con 2:  <UNIF:T::{Type}>
Hnormed 1:  [#Message = Basis.string]
Hnormed 2:  <UNIF:T::{Type}>
You may be using a disallowed attribute with an HTML tag.

The error is pointing to the fact that it can't work out the type of data.Message. The fix is to ensure we have an HTML form in the application with an action that submits to notifyMessage. Since this particular POST handler is being used by an external caller we need to create a dummy page with a form:

fun dummyForm () =
  return <xml>
    <body>
      <form>
        <textbox{#Message}/>
        <submit action={notifyMessage}/>
      </form>
    </body>
  </xml>

This enables the Ur/Web type checker to know that notifyMessage has POST data containing a 'message' field. dummyForm needs to be added to the .urs file containing the exported function signatures:

val dummyForm : unit -> transaction page

I've created an example application to test this in github. The main function for the application displays instructions on how to POST using curl and displays the messages currently stored in the database:

fun main () =
  rows <- listMessages ();
  return <xml><head>
    <title>Post Example</title>
  </head>
  <body>
    <p>Send a curl command like the following to add data
       to the messages table and refresh the page:</p>
    <p>curl -F "message=Hello" http://127.0.0.1:8080/notifyMessage</p>
    {rows}
  </body>
</xml>

It uses a listMessages helper function to do a SQL query on the table and outputs an HTML table row for each result in the query:

fun listMessages () =
  rows <- queryX (SELECT * FROM messages)
         (fn row => <xml><tr><td>{[row.Messages.Message]}</td></tr></xml>);
  return <xml><table>{rows}</table></xml>

To build, run and send post data using curl

$ git clone git://github.com/doublec/urweb-post-example
$ cd urweb-post-example
$ make
$ ./post.exe
$ curl -F "message=hello" http://127.0.0.1:8080/notifyMessage

To display posted messages go to http://localhost:8080/main in a web browser.

The downside with this workaround is we end up with a dummyForm page that is publically explosed. This could be prevented from being served by putting the web application behind a reverse proxy which doesn't expose it. This issue and workaround is a result from a discussion on the mailing list about POST.

Another issue that popped up during that discussion was that Ur/Web can only handle POSTing of HTML form data. If the Content-Type of the data is something else (like application/json) then it can't be handled with the current release of Ur/Web without going through some form of intermediate proxy to convert the data to what Ur/Web expects.

Hopefully these can be resolved in a later Ur/Web release.

Update 2010-12-19: The issues above look to be solved now according to the Ur/Web bug tracker.

Tags


This site is accessable over tor as hidden service mh7mkfvezts5j6yu.onion, or Freenet using key:
USK@1ORdIvjL2H1bZblJcP8hu2LjjKtVB-rVzp8mLty~5N4,8hL85otZBbq0geDsSKkBK4sKESL2SrNVecFZz9NxGVQ,AQACAAE/bluishcoder/-44/


Tags

Archives
Links