[more edits. Added "make open" jeremy@n-heptane.com**20080712002121] { hunk ./Main.lhs 19 -

HAppS (the Haskell Application Server framework) recently got a whole lot more awesome because it now supports HTML templating using HSP (Haskell Server Pages).

-

HSP is an extension to Haskell which allows you to use XML syntax in your Haskell source. It is implemented as a preprocessor, trhxs, which transforms the source into regular Haskell code.

+

HAppS (the Haskell Application Server framework) recently got a whole lot more awesome when it added support for HTML templating using HSP (Haskell Server Pages).

+

HSP is an extension to Haskell which allows you to use XML syntax in your Haskell source. It is implemented as a preprocessor, trhxs, which transforms the source into regular Haskell code. Unlike some templating systems, you are not restricted to simple string substitution, or a limited templating language. Using HSP we can use the full-power of Haskell in our templates.

hunk ./Main.lhs 22 -

In this quickstart guide I will give a brief overview of how I use HSP with HAppS. I assume you already know the basics of using HAppS and so I do not cover those aspects, unless they differ when using HSP. This quickstart is very heavy on the "what to do", but a bit light on the "why" portion. The biggest contribution is the working template, which gets you started on the right foot.

+

In this quickstart guide I will give a brief overview of how I use HSP with HAppS. I assume you already know the basics of using HAppS and so I do not cover those aspects, unless they differ when using HSP. This quickstart is very heavy on the "what to do", but a bit light on the "why" portion. The biggest contribution is the working example directory, which gets you started on the right foot.

hunk ./Main.lhs 29 - template to start your projects with.

+ template directory to start your projects with.< /p> hunk ./Main.lhs 43 -

and then point your browser at http://localhost:8000

+

and then point your browser at http://localhost:8000/

hunk ./Main.lhs 53 -

A Very Simple HSP Example

-

The following example highlights the basics of mixing XML and Haskell together. The hello function takes a String as an argument, and uses it to fill out an XML template. This example uses HSP, but not HAppS

+ hunk ./Main.lhs 57 -

Note the use of normal XML syntax in the hello function. If we want to evaluate a Haskell expression and insert it into the XML, then we need the special <% %> escape clause. If we had just written, <title> Hello, noun</title> then the title would have been Hello, noun.

-

We can evaluate any arbitrary expression inside the <% %>, provided HSP knows how to turn the result into an XML value. For example, in the body of the html, we have the simple expression map toUpper noun. So, this is not just simple string substitution.

-

By default, HSP knows how to handle String, Char, numbers, and some other common Haskell data-types. You can add additional class instances if you want to convert other datatypes (including your own) to XML automatically.

-

If we run this example, we get the following output:

-
-<html
-><head
-  ><title
-    >Hello, World</title
-    ></head
-  ><body
-  ><p
-    >Hello, WORLD</p
-    ></body
-  ></html
->
-
-

The formatting probably looks a bit funny to you -- but the web browser will read it fine. In HTML, the whitespace inside element tags can sometimes be significant. However, the whitespace inside an element tag is never significant. The rendering algorithm is designed to exploit those properties to ensure it never adds whitespace where you didn't explicit have it in the input file.

hunk ./Main.lhs 61 -
  • JSON data serialization - in order to pass data from HAppS to the HSP template, the data is serialized as a JSON object. This means you can design your HAppS backend to output JSON exclusively, and let the HSP portion take care of HTML creation. This can be useful if you want to extend your application to support AJAX or if you want to expose your API to third parties using JSON. +
  • JSON data serialization - in order to pass data from HAppS to the HSP template, the data is serialized as a JSON object. This can be especially useful if you want to extend your application to support AJAX or if you want to expose your API to third parties using JSON, because you can use a single unified JSON API for all three instead of having the JSON API be some extra thing that is tact on. hunk ./Main.lhs 65 -

    This is the basic template for creating a HAppS application using:

    +

    This is the basic framework for creating a HAppS application using:

    hunk ./Main.lhs 70 +
  • RJson for JSON marshalling hunk ./Main.lhs 87 -

    This main function is mostly boilerplate.

    +

    This main function is mostly boilerplate.

    hunk ./Main.lhs 93 -

    newStore just creates an IORef to a Map which will map template files to compiled executables.

    +

    newStore just creates an IORef to a Map which will map template source files to compiled object files.

    hunk ./Main.lhs 101 -

    Next we parse the command-line arguments to extract a default config file. We also enable validation using wdg-html-validator. In theory, validation should be enabled and disable via a command-line argument as well, but I have not added that functionality yet. (Note, this also requires my patches for validation, which are not yet in HAppS upstream. You can just comment out the validation stuff for now if you do not have my patches).

    +

    Next we parse the command-line arguments to extract a default config file. We also enable validation using wdg-html-validator. In theory, validation should be enabled and disable via a command-line argument, but I have not added that functionality yet. (Note, this also requires my patches for validation, which are not yet in HAppS upstream. You can just comment out the validation stuff for now if you do not have my patches).

    hunk ./Main.lhs 152 - accessed via HSP, client-side javascript, or 3rd parties. We + accessed via HSP, client-side javascript, or as a 3rd party API. We hunk ./Main.lhs 157 - runHSPHandle or the call to globalRead in - the page template will fail with the - error "Missing content-type".

    + runHSPHandle.

    hunk ./Main.lhs 168 -
  • A default mime-type, doctype, etc, to use if the template does not provide any values. -
  • The name of the template file relative to the directory passed to runHSPHandle +
  • default XMLMetaData (mime-type, doctype, etc), to use if the template does not provide any values. +
  • the name of the template file relative to the directory passed to runHSPHandle hunk ./Main.lhs 190 -

    Hopefully you have enough information to start experimenting with the template now. Have fun!

    +

    I hope you now have a basic idea of what is going on. The next + step is to extract the template directory and attempt to make your + own site.

    + +

    In this quickstart, our State and our JSON interface are very + closely related. In fact, they are just the HitCounter + type. For a simple application such as this one, Storing your JSON + objects directly in the State seems like a sensible approach. For + other sites, you will find that you want to have one set of + data types for your State, and a separate set of data types for your + Interface, though some data types may still be shared between the + two.

    hunk ./Makefile 13 + +# open the .html file in firefox +# probably will not work if path contains spaces, etc +open: $(BASENAME).html + (firefox -new-tab "file://$$(realpath $<)") hunk ./SimpleExample.lhs 2 -

    The following example highlights the basics of mixing XML and Haskell together. The hello function takes a String as an argument, and uses it to fill out an XML template. This example uses HSP, but not HAppS

    +

    The following example highlights the basics of mixing XML and Haskell together. This example uses HSP, but not HAppS.

    hunk ./SimpleExample.lhs 4 -

    The first thing to notice is the extra options we pass to GHC, namely -F -pgmF trhsx. This tells GHC (and GHCi) to call trhsx to pre-process the source code before trying to compile it. trhsx will take all the XML syntax and automatically translate it into normall Haskell code which the compiler can understand

    +

    The first thing to notice is the extra options we pass to GHC, namely -F -pgmF trhsx. This tells GHC (and GHCi) to call trhsx to pre-process the source code before trying to compile it. trhsx will automatically translate the XML syntax into normal Haskell code which the compiler can understand.

    hunk ./SimpleExample.lhs 13 -

    Note that if we did not have the <% %> around map toUpper noun, the page would say, "Hello map toUpper noun" instead of evaluating map toUpper noun and substituting the result in.

    +

    Next we see how to write a simple function which generates XML using the special XML syntax as well as dynamically creating some of the XML using ordinary Haskell expressions.

    hunk ./SimpleExample.lhs 29 +

    To use the XML syntax, we just use it -- no special escaping is required to insert XML in the middle of a Haskell expression. On the other hand, if we want to evaluate a Haskell expression and insert it into the XML, then we need the special <% %> escape clause. If we had just written, <title> Hello, noun</title> then the title would have been Hello, noun. Likewise, if we did not have the <% %> around map toUpper noun, the page would say, "Hello map toUpper noun" instead of evaluating map toUpper noun and substituting the result in.

    hunk ./SimpleExample.lhs 31 -

    Next we have a simple main which calls the the - hello function. The first line evaluates the - hello and gets back the generated XML. In the second - line, we use renderAsHtml to turn the XML into - HTML. renderAsHtml is a function which understands how - to turn XHTML into HTML. It does not check the validity of the input - or output. We will do that using a different mechanism.

    +

    We can evaluate any arbitrary expression inside the <% %>, provided HSP knows how to turn the result into an XML value. By default, HSP knows how to handle String, Char, numbers, and some other common Haskell data-types. You can add additional class instances if you want to convert other datatypes (including your own) to XML automatically.

    + + +

    Next we have a simple main. The first line evaluates + hello and gets back the generated XML. The second + line uses renderAsHtml to turn the XML into + HTML. renderAsHtml expects you to pass in valid XHTML + which it will transform into valid HTML. However, it does not check + the validity of the input or output. We will do that using a more + general mechanism in the HAppS code.

    hunk ./SimpleExample.lhs 49 -

    Note the use of normal XML syntax in the hello function. If we want to evaluate a Haskell expression and insert it into the XML, then we need the special <% %> escape clause. If we had just written, <title> Hello, noun</title> then the title would have been Hello, noun.

    -

    We can evaluate any arbitrary expression inside the <% %>, provided HSP knows how to turn the result into an XML value. For example, in the body of the html, we have the simple expression map toUpper noun. So, this is not just simple string substitution.

    -

    By default, HSP knows how to handle String, Char, numbers, and some other common Haskell data-types. You can add additional class instances if you want to convert other datatypes (including your own) to XML automatically.

    hunk ./SimpleExample.lhs 63 -

    The formatting probably looks a bit funny to you -- but the web browser will read it fine. In HTML, the whitespace inside element tags can sometimes be significant. However, the whitespace inside an element tag is never significant. The rendering algorithm is designed to exploit those properties to ensure it never adds whitespace where you didn't explicit have it in the input file.

    + +

    The formatting probably looks a bit funny to you -- but the web browser will read it fine. In HTML, the whitespace between the open and close tags is sometimes significant. However, the whitespace inside the open or close tag itself is never significant. The rendering algorithm is designed to exploit those properties to ensure it never adds significant whitespace where you didn't explicit have it in the input file.

    hunk ./State.lhs 3 -

    For this example, our State will just be HitCounter. There are only two things that make this code different from typical non-HSP HAppS applications.

    +

    In this quickstart our State will just be HitCounter. There are only two things that make this code different from typical non-HSP HAppS applications.

    hunk ./pages/Index.lhs 8 - that.

    + that. happs-hsp-template will automatically pass those + flags when runing the template, but adding them explicitly means we + can easily load the page into GHCi for debugging purposes.

    hunk ./pages/Index.lhs 13 + + + hunk ./pages/Index.lhs 31 - it always has the type Web XML. This is similar to how + it always has the type Web XML. (If you use HSP + without HAppS then you will want HSP XML instead of Web XML). This is + similar to how hunk ./pages/Index.lhs 45 -

    Our page function does three things.

    +

    Our page function does fours things.

    hunk ./pages/Index.lhs 47 -
  • Sets the XML meta-data to html4Strict. This will causes pages to be rendered as HTML (instead of XML), and will set the DOCTYPE to HTML 4.01 Strict. -
  • Next it reads the "hits" value from the local environment. -
  • Then it calls the page' function +
  • Sets the XML meta-data to html4Strict. This will: + +
  • reads the "hits" value from the local environment. +
  • Uses the JSON API to determine if it should say, "time" or "times" +
  • Calls the page' function hunk ./pages/Index.lhs 64 +

    globalRead simulates an HTTP request to the HAppS + server. In this example, we just do a simple GET request + to /json/times, but any type of HTTP request can be simulated.

    + +

    If globalRead returns "Missing + content-type", it is probably because your JSON API is not + inside the runHSPHandle call.

    + hunk ./pages/Index.lhs 75 - gives you the option of calling the function page' - during development.

    + gives you the option of calling the page' function.

    hunk ./pages/Interface.lhs 2 -

    JSON, short for Javascript Object Notation, is a lightweight data-interchange format. It's primary appeal is that it is natively supported by Javascript, meaning you can create JSON objects directly in the javascript program text, and easily convert between JSON and javascript objects at runtime.

    +

    JSON, short for Javascript Object Notation, is a lightweight data-interchange format. It's primary appeal is that it is natively supported by Javascript. This means you can create javascript objects using JSON notation in the javascript program text, and you can easily convert between JSON and javascript objects at runtime.

    hunk ./pages/Interface.lhs 4 + + hunk ./pages/Interface.lhs 13 -

    Note the use -F -pgmF in the OPTIONS_GHC pragma:

    -

    The {-# OPTIONS_GHC -cpp #-} is an artifact from the way this quickstart guide was constructed as does not appear in the final template

    +

    Note the, -F -pgmF ./identity.sh, in the OPTIONS_GHC pragma:

    hunk ./pages/Interface.lhs 29 -

    Instead of running this file through trhsx we are running it through identity.sh. Unfortunately, this is just an ugly hack. The current implementation of trhsx does not understand how to parse Template Haskell. However, the happs-hsp-template attempts to run all the modules through trhsx, so we need this hack to override it. You will need a copy of the identity.sh in the top-level directory and the pages subdirectory. The identity.sh script is a simple preprocessor which leaves the contents unmodified. It is implemented like this:

    +

    Instead of running this file through trhsx we are running it through identity.sh. Unfortunately, this is just an ugly hack. The current implementation of trhsx does not understand how to parse Template Haskell. However, the happs-hsp-template attempts to run all the modules through trhsx, so we need this hack to override that behaviour. You will need a copy of the identity.sh in the top-level directory and the pages subdirectory. The identity.sh script is a simple preprocessor which leaves the contents unmodified. It is implemented like this:

    hunk ./pages/Interface.lhs 38 -

    Next we define an type which will be used as JSON data.

    +

    Next we define a type which will be used as JSON data:

    hunk ./pages/Interface.lhs 52 -

    Because we might want to store HitCounter in the HAppS State we also deriveSerialize for it, and create a Version instance.

    +

    Because we might want to store HitCounter in the HAppS State we also deriveSerialize for it, and create a Version instance. In theory this could also be useful if clients attempt to pass JSON data back to the server using an older format. However, I have no idea how to handle that in practice.

    hunk ./pages/Interface.lhs 56 -> }