Reputation:
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
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