Last time I spent a lot of time getting something not very impressive set up - a web server that responds PONG
when you request path /ping
from it.
In this post I'm going to make that ping a bit prettier, by using HTML instead of plain text.
I'm going to use a package called blaze-html
. It isn't the only way to generate HTML and if I was starting this project all over again I'd possibly use Heist instead, which takes an extremely different approach. But hey ho what's done is done.
If you know basic HTML (and of course you do - this isn't 1992), then you should hopefully find this not hard at all.
Get Blaze installed by by adding dependencies on blaze-html
and servant-blaze
(like the last post added servant-server
and warp
). The first of these is Blaze proper and the second is some glue that makes it work with Servant. (there are a pile of servant-* packages for gluing in other packages, some of which I'll talk about later). As you may have noticed from the previous post, I like to run stack build
to install dependencies right away, but again you don't have to - they'll get done next time you run that command.
import Servant.HTML.Blaze as SB import qualified Text.Blaze.Html5 as B
I want the server to still support the old /ping
plain text endpoint, but have a new endpoint /htmlping
which returns a pretty HTML version of PONG
.
First modify the API to say that it has two endpoints: make a new type API
which is comprised of both the old
PingAPI
and a new HtmlPingAPI
; and change the api
and server
type signatures to refer to that new API
type. Use the :<|>
type operator to compose the two API descriptions together.
type PingAPI = "ping" :> S.Get '[S.PlainText] String type HtmlPingAPI = "htmlping" :> S.Get '[SB.HTML] B.Html type API = PingAPI :<|> HtmlPingAPI api :: S.Proxy API server :: S.Server API
If you try to compile this now, you should get a type error:
/home/benc/src/cwfh28/reg/app/Main.hs:32:10: error: * Couldn't match type `S.Handler String' with `S.Handler [Char] :<|> S.Handler (blaze-markup-0.8.2.0:Text.Blaze.Internal.MarkupM ())' Expected type: S.Server API Actual type: S.Handler String * In the expression: handlePing In an equation for `server': server = handlePing | 32 | server = handlePing | ^^^^^^^^^^
... which is saying that although you claim to expose an API with both /ping
returning a String, and /htmlping
returning HTML, your implementation in server
only handles the first case. As is the case with most non-trivial Haskell type checking errors, you'll have to stare at this for a while to put the pieces together. But it might be worth understanding how that error actually says what it says.
What needs to happen is that server needs a new implementation, to handle a regular ping and to handle an HTML ping.
server = handlePing :<|> handleHtmlPing handleHtmlPing :: S.Handler handleHtmlPing = return "Some basic HTML"
You also need to add a new language extension to packages.yaml, called "OverloadedStrings" - next to where you added DataKinds
and TypeOperators
last time.
So with this in place, building and running the app should respond with plain text on /ping
and with some very plain HTML on /htmlping
- don't believe me about the second one? Well there isn't anything to see yet aside from a changed Content-type:
header if you go poking down at the protocol level. Maybe your browser will display it in a different font.
So let's generate something a bit less trivial - Here's a different version of handleHtmlPing
:
handleHtmlPing :: S.Handler B.Html handleHtmlPing = return $ B.docTypeHtml $ do B.head $ do B.title "HTMLPONG" B.body $ do B.h1 "HTML Ping Response" B.p "It seems to work ok"
which will generate some HTML code that will render something like this:
HTML Ping Response
It seems to work ok
with raw HTML something like this: (modulo formatting)
<html> <head> <title>HTMLPONG</title> </head> <body> <h1>HTML Ping Response</h1> <p>It seems to work ok</p> </body> </html>
Hopefully it's apparent that the Haskell code structure mirrors the structure of the generated HTML. Blaze provides implementations of common tags, taking the body of that tag as an argument; and multiple tags can be sequenced into a single piece of HTML using do
.
It's a bit awkward using Haskell syntax to write HTML, if you're used to writing normal HTML; and in the above case there isn't much benefit. But you have access to the whole Haskell language to build the HTML now - you can use if
and variables and Control.Monad
and write your own functions as macros. etc.
Next, I'll put aside the web side of things and look at representing our application's registration data in Haskell and storing that data in a PostgreSQL database.
commit 8e1c944d gives the changes for this post.
No comments:
Post a Comment