Rather than using Java servlet objects directly in your handler, it
may be better to create some middleware to pull the information out of
the servlet context and request, and add them to the Clojure request
map.
> Few valid use cases:
> 1. I want to generate absolute URIs (images, css, js etc) inside the
> HTML template -- I need the context path there as a prefix. This is
> absolutely required when I don't know under what context path my app
> will be deployed as a WAR.
I'd suggest using some middleware:
(defn wrap-context-path [handler]
(fn [request]
(let [path (-> request :servlet-request .getContextPath)
request (assoc request :context-path path)]
(handler request))))
Apply this middleware only when you wish to deploy the application as
a war using the defservice macro. Then the :context-path key will be
assigned to the servlet context path. For development usage, the
:context-path key does not exist, and therefore (request
:context-path) will be nil. The str function represents nils as blank
strings, which should be what you want in this case.
> 2. I want to use the servlet-context as "application scope" -- I need
> the servlet context there.
I'm not entirely sure what you mean by this, but you can take
information from the servlet context, and place it either in the
request map, or bind it to a thread-local variable. Ring handler
functions are guaranteed to be executed in one thread (unless you
explicitly create new threads or using concurrency primitives like
agents).
- James
That would break compatibility between adapters.
I can see why you may want to access servlet keys when deploying as a
war, as you may want access to information from the server like the
servlet context path. However, I can't see why you'd want them when
using an inline adapter, because in that case you control the server.
There's nothing it can tell you that you don't already know.
- James
Sorry about the late reply. Forgot to star this.
I'm afraid I'm still not following what you're trying to do. First,
why are you running Jetty during your unit tests? You should only be
running Jetty during your system tests.
Second, I still don't know why you need all this servlet information
when you're running Jetty as an adapter. When running the Jetty
adapter, you already know the context path, because it's fixed at "".
Maybe if you provide some code examples to explain?
- James
I understand this. Finding the context-path is useful if you want to
integrate with an existing Java system. For instance, you might write
something like this:
(ns example.servlet
(:gen-class :extends javax.servlet.http.HttpServlet)
(:use ring.util.servlet
[example.handler :only (app)]))
(defn wrap-context-path [handler]
(fn [request]
(let [path (-> request :servlet-request .getPathInfo)]
(handler (-> request
(assoc :raw-uri (request :uri))
(assoc :uri path))))))
(defservice
(wrap-context-path app))
In the above code, we're changing the :uri key so that it points to
the path info part of the servlet, rather than the full URI. This
means that when the namespace is compiled into a servlet, it will
respect the context path.
But when testing it with the Jetty adapter, the context path is always
blank, so there is no need to use the wrap-context-path middleware. In
our local environment, we run the application like:
(run-jetty app {:port 8080})
The key idea is that we should isolate the Ring and Servlet parts as
much as possible, so that they are only loosely coupled. When using
Ring adapters, we need not worry about the context path; when
integrating with a Java Servlet holder, we can write middleware to
convert Servlet-specific information into keys on the request map.
> Running Jetty is useful when unit testing RESTful services. The Jetty
> server can be started and stopped for each test case.
What you describe are system tests, not unit tests.
Unit tests are concerned with testing the smallest individual parts of
an application (i.e. units). For instance, let's say I have a route
that looks something like this:
(defroutes app
(POST "/book/:isbn" [isbn]
(show-book isbn)))
Then we might have a unit test like this:
(deftest app-isbn-unit
(expect [show-book (has-args "1234")]
(app {:request-method :post, :uri "/book/1234"})))
Note that this unit test doesn't even care what show-book returns; it
just cares that it's being called. Unit tests should be tiny, testing
one thing only, but also numerous.
Integration tests are concerned with testing the interactions between
units. An integration test might look like this:
(deftest app-isbn-integration
(let [resp (app {:request-method :post, :uri "/book/1234"})]
(is (= (resp :status) 200))
(is (re-find #"ISBN 1234" (resp :body)))))
In this case, we're testing that the "/book/1234" book returns the
right response, so this test covers the "/book/:isbn" route, and the
"show-book" function all in one.
Finally, a system test checks your entire application is working as a
whole. For instance, we might run our application in a web server, and
then have a HTTP client firing off requests, and checking the
responses match.
- James