haskell-servant / haskell-servant/servant
Trailing slashes in CaptureAll router behave unpredictably
- Dominant language
- Haskell
- Stars
- 2k
- Forks
- 427
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 5
Description
Consider the following program:
```haskell
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Repo where
import Control.Monad.Except (throwError)
import Data.List (isSuffixOf)
import Data.Proxy (Proxy(..))
import Data.Text (Text)
import GHC.Generics (Generic)
import Network.HTTP.Types.Header (hLocation)
import Network.Wai.Handler.Warp (run)
import Servant.API
import Servant.Links
import Servant.Server
data API mode = API
{ myroute :: mode :- "test" :> CaptureAll "segs" Text :> Get '[JSON] [Text]
}
deriving Generic
server :: [Text] -> Handler [Text]
server ts =
case isSuffixOf [""] ts of
False ->
throwError err303
{ errHeaders = pure
( hLocation
, mappend "/" $ toHeader $ fieldLink myroute $ ts <> [""]
)
}
True -> pure ts
main :: IO ()
main = run 8081 $ serve (Proxy @(NamedRoutes API)) $ API server
```
which 303 redirects `test/:stuff` to `test/:stuff/`. This works when `:stuff` is nonempty, eg `test/abc` correctly gets redirected to `test/abc/` (and returns `["abc", ""]`).
However, when `:stuff` is empty, this server loops, redirecting the page back to itself:
```
Request
GET /test/ HTTP/1.1
Host: localhost:8081
Pragma: no-cache
---
Response
HTTP/1.1 303 See Other
Location: /test/
```
Contributor guide
Research direction
Start by running the provided Haskell program and reproducing the redirect loop for GET /test/. Read the CaptureAll routing and link-generation paths involved in that request. Done means a trailing slash is handled without redirecting /test/ to itself, while the nonempty case still redirects test/abc to test/abc/ and returns the expected segments.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100