5

I have a simple hello world Servant application. I need to add some static or dynamic html pages to it. How can I do that? In the documentation it's not mentioned. Note I don't want to create a html layout in Haskell code, I want Haskell to show the html pages that's already created.

UPDATE:

How can I combine this:

type MyApi = "/" :> Raw

server :: Server MyApi
server = serveDirectory "static/" -- index.html, about.html 

with what I have already:

  type API = 
    "api" :> "items" :> Get '[JSON] [MyData] :<|>
    "api" :> "items" :> Capture "id" Int :> Get '[JSON] MyData


  app :: Application
  app = serve api server

  api :: Proxy API
  api = Proxy

  server :: Server API
  server = getItems :<|> getItem

  startApp :: IO ()
  startApp =  run 1234 app

UPDATE2:

Working:

type API = 
    "api" :> "items" :> Get '[JSON] [MyData] :<|>
    "api" :> "items" :> Capture "id" Int :> Get '[JSON] MyData :<|>
    Raw

Not working, no response at all:

type API = 
    "api" :> "items" :> Get '[JSON] [MyData] :<|>
    "api" :> "items" :> Capture "id" Int :> Get '[JSON] MyData :<|>
    "/" :> Raw

-- or

type API = 
    "api" :> "items" :> Get '[JSON] [MyData] :<|>
    "api" :> "items" :> Capture "id" Int :> Get '[JSON] MyData :<|>
    "" :> Raw

I wonder why?

  • You can do `serveDirectory` if your static pages are already in correct structure. – zakyggaps Apr 02 '16 at 12:04
  • @zakyggaps, updated. –  Apr 02 '16 at 12:06
  • 1
    Servant APIs will be matched in order, top to bottom. You can put it at the last line. – zakyggaps Apr 02 '16 at 12:12
  • @zakyggaps, in the 1st "server" I have serveDirectory "static/" and in 2nd I have "getItems :<|> getItem" . How can I combine them? –  Apr 02 '16 at 12:21
  • You cannot do that: `:<|>` is right associative, if `"/" :> Raw` is the last case you have to define your API with all the three cases in it like `case1 :<|> case2 :<|> case3`. – zakyggaps Apr 02 '16 at 12:29
  • @zakyggaps, what's the point for you of commenting here if you can write an answer? –  Apr 02 '16 at 12:34
  • Sorry, I was searching for a duplicate but didn't find one. I will try organize an answer. – zakyggaps Apr 02 '16 at 12:37

1 Answers1

7

how to combine REST API and static html pages?

You may serve the directory containing your static website at root path by serveDirectory. It has to be the last case in your Servant API or other cases will never be matched.

type API = 
    "api" :> "items" :> Get '[JSON] [MyData] :<|>
    "api" :> "items" :> Capture "id" Int :> Get '[JSON] MyData :<|>
    Raw

api :: Proxy API 
api = Proxy

server :: Server API 
server = getItems
    :<|> getItem 
    :<|> serveDirectory "static/"

Also if any static page name crashes with your APIs it will be shadowed.


why isn't it "/" :> Raw?

Looks like my browser cached some static pages. "/" :> Raw gives no response under /index.html after cleaned the cache.

a string literal in api will be encoded to legal uri parts first, therefore "/" will be "%2F" and your files are mapped to /%2F/index.html and so on.


do you know how can I handle the root case?

To serve a response at root path, you may define a Get endpoint with no prefix:

type API = Get '[HTML] RawHtml

It can be anywhere in your API except the last line.

To serve a local file as html response you have to distinguish the file from other bytestrings, maybe wrap it in a newtype:

newtype RawHtml = RawHtml { unRaw :: BS.ByteString }

-- tell Servant how to render the newtype to html page, in this case simply unwrap it
instance MimeRender HTML RawHtml where
    mimeRender _ =  unRaw

and in your controller:

-- ...
:<|> fmap RawHtml (liftIO $ BS.readFile "your/file/path.html")

Or if the page has another address already, you can redirect users there:

-- ...
:<|> throwError err301 { errHeaders = [("Location", "index.html")] }

it already returns index.html. Hm, why exactly index.html?

serveDirectory calls a wai application staticApp with a setting defaultFileServerSettings. In that setting users will be redirected to index.htm or index.html if something went wrong:

defaultFileServerSettings root = StaticSettings
    { ssLookupFile = fileSystemLookup (fmap Just . hashFile) root
    , ssMkRedirect = defaultMkRedirect
    , ssGetMimeType = return . defaultMimeLookup . fromPiece . fileName
    , ssMaxAge = NoMaxAge
    , ssListing = Just defaultListing
    , ssIndices = map unsafeToPiece ["index.html", "index.htm"]
    , ssRedirectToIndex = False
    , ssUseHash = False
    , ssAddTrailingSlash = False
    , ss404Handler = Nothing
    }
zakyggaps
  • 3,070
  • 2
  • 15
  • 25
  • why isn't it `:<|> "" :> Raw` or `:<|> "/" :> Raw`? why merely `:<|> Raw`? –  Apr 02 '16 at 12:44
  • Both `Raw` and `"/" :> Raw` will point to the root path. I chose the shorter. – zakyggaps Apr 02 '16 at 12:47
  • actually `:<|> "" :> Raw` and `:<|> "/" :> Raw` aren't working. The server doesn't return anything, like it's not found. I wonder why? –  Apr 02 '16 at 12:47
  • @OskarK. I cannot reproduce that. `"/" :> Raw` and `Raw` have the same consequence as I tried. – zakyggaps Apr 02 '16 at 12:57
  • do you know how can I handle the root case, that is, when I request "localhost:1234", how can I return the page "index.html"? Meaning, I want to return "index.html" for both "localhost:1234" and "localhost:1234/index.html" –  Apr 02 '16 at 13:11
  • actually, it already returns index.html. Hm, why exactly index.html? –  Apr 02 '16 at 13:13