Web and Database Programming

May 15, 2014 - CS240H

Before we start…

If you want to follow along on your laptop towards the end:

You’ll also need to have PostgreSQL installed

Agenda

  1. Intro/motivation

  2. Modeling a web application in Haskell

  3. Build a content management system

Why should you care about web programming?

How do you people write a web app?

But why dynamic languages?

But why dynamic languages?

Less verbose

e.g. no type declerations

x = 123

def incr(y)
  y + 1
end

vs

protected static int x = 123;

public static int incr(int y) {
  return y + 1;
}

But why dynamic languages?

Advanced features

like closures

Array.map(myarr, new Runnable() {
  public void run(int elm) {
    return elm * 42;
  }
})

vs.

myarr.map {|elm| elm * 42}

But why dynamic languages?

Other less compelling reasons

“When rendering web pages, often you have very many components interacting on a web page. You have buttons over here and little widgets over there and there are dozens of them on a webpage, as well as possibly dozens or hundreds of web pages on your website that are all dynamic. […] using a statically typed language is actually quite inflexible. […] like the whole system has to type check just to be able to move a button around”
- Nick Kallen from Twitter

Is it really about dynamism?

No type declerations (but still typed)

x = 123 -- :: Num a => a

incr y = y + 1 -- :: Num a => a -> a

Closures

map (* 42) myarr

A lot of the arguments are really about weaknesses in Java et al.

Modeling a web application in Haskell

Boilerplate code: http://cs240h.scs.stanford.edu/Application.hs

Modeling a web application in Haskell

```haskell
data Request = Request {pathInfo :: [String], requestMethod :: Method, ...}

data Response = Response Status [Header] String

type Application = Request -> IO Response
```

We’ve just implemented the WAI package – “Web Application Interface”!

The WAI package

The WAI package

data Request = Request {
     requestMethod        :: Method
  ,  httpVersion          :: HttpVersion
  ,  rawPathInfo          :: ByteString
  ,  rawQueryString       :: ByteString
  ,  requestHeaders       :: RequestHeaders
  ,  isSecure             :: Bool
  ,  remoteHost           :: SockAddr
  ,  pathInfo             :: [Text]
  ,  queryString          :: Query
  ,  requestBody          :: Source IO ByteString
  ,  vault                :: Vault
  ,  requestBodyLength    :: RequestBodyLength
  ,  requestHeaderHost    :: Maybe B.ByteString
  ,  requestHeaderRange   :: Maybe B.ByteString
  }

data Response
    = ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)
    | ResponseBuilder Status ResponseHeaders Builder
    | ResponseSource Status ResponseHeaders (forall b. WithSource IO (C.Flush Builder) b)
    | ResponseRaw (forall b. WithRawApp b) Response

type Application = Request -> IO Response

A really simple application

Let’s build the simplest application that displays something in a browser

$ cabal install wai warp
module Main where

import qualified Data.ByteString.Lazy.Char8 as L8
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp (run)

app :: Application
app req = return $ responseLBS status200 [] $ L8.pack "Hello, World"

main :: IO ()
main = do
  run 3000 app

Let’s build a CMS!

  1. (Very) quick intro to Simple

  2. (Very) quick intro to postgresql-orm

  3. Write some code

Simple - a web framework in Haskell

Simple is a web framework with one type:

newtype Controller s a = Controller {
  runController :: s -> Request -> IO (Either Response a, s)
  }

instance Monad Controller
instance Applicative Controller
instance MonadIO Controller

Some Simple combinators

respond :: Response -> Controller s a
okHtml :: ByteString -> Response
notFound :: Response

respond $ okHtml "Hello world"
request :: Controller s Request
controllerState :: Controller s s
queryParam' :: Parseable p => Controller s p
parseForm :: Controller s ([Param], (ByteString, FileInfo ByteString))

Some Simple combinators

-- Match on next dir in path
routeName :: Text -> Controller s () -> Controller s ()
routeName "articles" $ ...

-- Treat first dir in path as query param
routeVar :: Text -> Controller s () -> Controller s ()
routeName "articles" $ routeVar "name" $ ...

-- Match whole pattern of path
routePattern :: Text -> Controller s () -> Controller s ()
routePattern "/articles/:name" $ ...

-- Match if no path left
routeTop :: Controller s () -> Controller s ()

-- Match on request method
routeMethod :: Method -> Controller s () -> Controller s ()
routeMethod GET $ routePatter "/articles/:name"

-- Match hostname
routeHost :: ByteString -> Controller s () -> Controller s ()

Higher-level Simple combinators

Common case is to match on method and a particular path pattern:

get :: Text -> Controller s () -> Controller s ()
get ptrn ctrl = routeMethod GET $ routePattern ptrn ctrl

post :: Text -> Controller s () -> Controller s ()
post ptrn ctrl = routeMethod POST $ routePattern ptrn ctrl

So a typical small app might look like:

myapp :: Controller s ()
myapp = do
  get "/" $ respond $ okHtml "Hello World"
  get "/foo" $ respond $ okHtml "bar"

PostgreSQL ORM

data Article = Article
  { articleId :: DBKey
  , articleTitle :: Text
  , articleBody :: Text
  , articleShortName :: Text }
class Model a where
  modelInfo :: ModelInfo a
  modelRead :: RowParser a
  modelWrite :: a -> [Action]

data DBKey = DBKey !Int64 | NullKey

data ModelInfo a = ModelInfo {
    modelTable :: ByteString
  , modelColumns :: [ByteString]
  , modelPrimaryColumn :: Int
  , modelGetPrimaryKey :: a -> DBKey }

PostgreSQL ORM

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics

data Article = Article
  { articleId :: DBKey
  , articleTitle :: Text
  , articleBody :: Text
  , articleShortName :: Text } deriving (Show, Generic)

instance Model Article
save :: Model a => Connection -> a -> IO ()
findAll :: Model a => Connection -> IO [a]
findRow :: Model a => Connection -> DBRef a -> IO (Maybe a)

OK, let’s get to coding:

$ cabal install simple
$ smpl create my_cms