Question: How to restrict an API to a particular virtual host (value of the request "Host:" header)?
My server hosts:
- A redirect from the "/" path to a different host that describes the project
- The project's backend API under "/prefix"
- Some static content (paths other than "/" or the API prefix.
I am adding a variant of "3" which is a virtual host for another name that will serve static content under a different directory based on the incoming "Host:" header. This works.
However, I'd prefer to not serve either the redirect or the API to requests for the alternative name. How can this be done?
type Blank = Headers '[Header "Location" String] NoContent
type Redirect = Verb 'GET 301 '[OctetStream] Blank -- Redirect GET /
type Backend = "prefix" :> Capture "input" Text :> Get '[JSON] Result
type Files = Header' '[Required, Strict] "Host" String :> Raw
type API = Redirect
:<|> Backend
:<|> Files
This is currently served via the below, which is only host-specific for the static content. How would I restrict the redirect and the API to just the default vhost, with all other hosts seeing just the Files API (or perhaps some day a different host-specific router)?
router = redirect :<|> backend :<|> serveStatic rootPath
where
redirect = pure $ addHeader redirURL NoContent
backend input = ...
-- Choose a host-specific root if applicable, else use default.
serveStatic :: forall m. FilePath -> String -> S.ServerT Raw m
serveStatic root hostport
| Just root' <- rootFor $ break (== ':') $ map toLower hostport =
S.serveDirectoryWith
$ W.webAppSettingsWithLookup root' $ getEtag maxAge
| otherwise =
S.serveDirectoryWith
$ (W.webAppSettingsWithLookup root $ getEtag maxAge)
{ W.ssMaxAge = maybe W.NoMaxAge W.MaxAgeSeconds maxAge }
P.S. I also didn't know how to reject malformed Host: headers with a 401 or suitable error code. So settled to just ignore them. Perhaps something I could do with RawM, but it is not immediately obvious how to use serveDirectoryWith as a handler for RawM.
Hmmm, it's an interesting problematic. I think my first reflex would be to do routing at the reverse proxy level (nginx, Apache, caddy, etc), and expose endpoints with specific audiences in mind.
Fixed by https://github.com/haskell-servant/servant/pull/1800. @vdukhovni Please try it out. :)