I use embedded Jetty 9.3.8.v20160314 from Scala (Scala is incidental here, this is a pure Jetty question), configured like this:
val loginService = new RepoLoginService(usersRepository, signInsRepo)
val server = new Server(8080)
server.addBean(loginService)
val security = new ConstraintSecurityHandler()
server.setHandler(security)
val constraint = new Constraint()
constraint.setName("auth")
constraint.setAuthenticate(true)
constraint.setRoles(Array[String]("user", "admin"))
val mapping = new ConstraintMapping()
mapping.setPathSpec("/*")
mapping.setConstraint(constraint)
security.setConstraintMappings(Collections.singletonList(mapping))
security.setAuthenticator(new BasicAuthenticator())
security.setLoginService(loginService)
val mapper = prepareJacksonObjectMapper
val listAccountsController = new ContextHandler("/api/accounts")
listAccountsController.setHandler(new ListAccountsController(mapper, accountsRepository))
val importBankAccountsController = new ContextHandler("/api/bank-account-transactions/import")
importBankAccountsController.setHandler(new ImportBankAccountTransactionsController(mapper, bankAccountTransactionsRepo))
val contexts = new ContextHandlerCollection()
contexts.setHandlers(Array(listAccountsController, importBankAccountsController))
security.setHandler(contexts)
server.start()
server.join()
Notice the context for the listAccountsController
: /api/accounts
. When I curl /api/accounts
, I receive a redirect to /api/accounts/
(note the trailing slash):
$ curl --silent --verbose --user francois:francois http://localhost:8080/api/accounts
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'francois'
> GET /api/accounts HTTP/1.1
> Host: localhost:8080
> Authorization: Basic ZnJhbmNvaXM6ZnJhbmNvaXM=
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Sun, 22 May 2016 03:54:44 GMT
< Location: http://localhost:8080/api/accounts/
< Content-Length: 0
< Server: Jetty(9.3.8.v20160314)
<
* Connection #0 to host localhost left intact
I tried adding a call to clearAliasChecks()
, which I'm not sure is even related to this. I also tried changing mapping.setPathSpec("/*")
to mapping.setPathSpec("/")
, because I read an answer on Tomcat not adding the trailing slash, but this probably doesn't apply to me anyway.
What must I change to prevent the trailing slash?
The way you have defined your Contexts is the reason for that.
A ContextHandler
is a "root" to a set of resources, and that root is defined to always be a resource directory (in structure).
Since you defined ContextHandler("/api/accounts")
and accessed /api/accounts
the implementation "helps" you by making /api/accounts/
the correct Context root.
Imagine this ...
val b = ContextHandler("/a/b")
val a = ContextHandler("/a")
- which implements (internally) support for sub-resource /b
/a/b
, which context are you in? - Per spec, you are in context a
/a/b/
, then you are in context b