Friday, February 2, 2018

A Web Server That Only Responds To Pings (1)

The main user interface to this thing is going to be the web, because it's going to mostly be a web form.

For the purposes of this post, 'web' means 'serving HTTP'; and I'm going to begin by getting a simple web server running that does pretty much nothing but respond "PONG" if you request a URL "yourcomputer:8080/ping".

Most recently the package that I used for web serving in a real job was 'servant', and so that's what I'm going to use here.

If you read the docs, they seem very focused on serving "APIs" by which they mostly mean sending and receiving JSON requests and responses. But servant isn't tied to that at all, and it's pretty straightforward to do regular human-facing requests and responses.

So let's fire up stack (You'll have to work out how to install that yourself) to initialise a new project called 'reg':

$ stack new reg
[...]
$ cd reg
$ stack build
[...]
$ stack exec reg-exe
someFunc

Depending on what you've done with stack before, this may download a little or a lot. I'm "lucky" enough to have a 5.1Gb ~/.stack directory caching a bunch of stuff already, and soon you will be too.

Anyway, let's get servant downloaded and installed.

Open "package.yaml", which is like the traditional reg.config but gratuitously reformatted into yaml. Find executables/reg-exe/dependencies, where you should find a dependency on `reg` and add a new line there appropriately indented and punctuated for `servant-server`.

executables:
  reg-exe:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - reg
    - servant-server

`stack build` again now if you like - it'll download and build a whole load of dependencies now, but the `reg-exe` program won't do anything different yet. Or don't `stack build` right now and leave it till later.

Like I said, the servant docs like to talk about APIs quite a lot, and so we need do define an API for the web server that we're going to be running. For now, that means defining that the URL path /ping will exist, that you can run a regular HTTP GET request against that URL (which is what a web browser does when you point a web server at a URL), and that what will come back will be a plain text string.

So fire up your favourite text editor on (or use some other tool to modify) app/Main.hs and insert the following:

Next to the existing `import` statement, add these new imports:

import Servant ( (:>) )
import qualified Servant as S

and somewhere like the bottom add this type definition:

type PingAPI = "ping" :> S.Get '[S.PlainText] String

We also need to enable some language extensions, so add this to the reg-exe section in package.yaml:

    default-extensions:
    - DataKinds
    - TypeOperators

You should be able to stack build again here, but again stack exec reg-exe won't behave any differently.

What is described so far is that there will be a /ping URL, and that when you make an HTTP GET against that URL, you'll be back some Haskell String value, encoded as plain text (not HTML or anything else fancy). But there's no description of what that String should be. And nothing to launch the web server to actually serve that URL.

To handle this ping API, we need to define something with the type Handler String, which will handle a request and return an appropriate String. For example:

handlePing :: S.Handler String
handlePing = return "PONG"

The last bit of getting a web server set up is actually running the server. Lose the existing main function and stick in this boilerplate:

Imports:

import qualified Network.Wai.Handler.Warp as W

and this code:

api :: S.Proxy PingAPI
api = S.Proxy

app = S.serve api server

server :: S.Server PingAPI
server = handlePing

main :: IO ()
main = W.run 8080 app

Also add in a dependency in package.yaml for the warp package.

Now stack build and stack exec reg-exe. The code will just sit there silenty waiting. Point your web browser to http://localhost:8080/ping and you should see a single PONG in your browser - that's the String returned by handlePing. Fiddle with handlePing and you can make it return different things. (if you want to be fancy, you can liftIO arbitrary IO stuff...)

You can get the code for this post on github: https://github.com/benclifford/cwfh28/tree/21c93c84c64aed9e8a9983c3583ddbf373656ce4

Next, I'll talk about generating HTML and building an HTML-enhanced ping endpoint.

commit 21c93c84 in the github respository shows what you should end up with by the end of this post - including both files manually edited and those created by stack.

No comments:

Post a Comment