user266003
user266003

Reputation:

Html page in Servant -- how to combine REST API and static html pages?

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?

Upvotes: 5

Views: 2146

Answers (1)

zakyggaps
zakyggaps

Reputation: 3080

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
    }

Upvotes: 7

Related Questions