Accessing HttpServletRequest object in the request-map

278 views
Skip to first unread message

Shantanu Kumar

unread,
Sep 3, 2010, 6:25:02 AM9/3/10
to Ring
Hi,

I notice the servlet/merge-servlet-keys function adds the servlet-
specific objects to the request map. This is used by the servlet/make-
service-method function while creating a servlet.

These objects may be also useful inside Compojure/or-other-lib route
handlers (to get context-path, or servlet-context etc). Is there a way
to start the Jetty server (ring-jetty-adapter) with the servlet keys
merged into the request map? I understand this may tie the app to
servlets, but giving a choice to do so may be useful.

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.
2. I want to use the servlet-context as "application scope" -- I need
the servlet context there.

Regards,
Shantanu

James Reeves

unread,
Sep 5, 2010, 10:01:23 AM9/5/10
to ring-c...@googlegroups.com
On 3 September 2010 11:25, Shantanu Kumar <kumar.s...@gmail.com> wrote:
> These objects may be also useful inside Compojure/or-other-lib route
> handlers (to get context-path, or servlet-context etc). Is there a way
> to start the Jetty server (ring-jetty-adapter) with the servlet keys
> merged into the request map? I understand this may tie the app to
> servlets, but giving a choice to do so may be useful.

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

Shantanu Kumar

unread,
Sep 5, 2010, 4:47:53 PM9/5/10
to Ring


On Sep 5, 7:01 pm, James Reeves <jree...@weavejester.com> wrote:
> On 3 September 2010 11:25, Shantanu Kumar <kumar.shant...@gmail.com> wrote:
>
> > These objects may be also useful inside Compojure/or-other-lib route
> > handlers (to get context-path, or servlet-context etc). Is there a way
> > to start the Jetty server (ring-jetty-adapter) with the servlet keys
> > merged into the request map? I understand this may tie the app to
> > servlets, but giving a choice to do so may be useful.
>
> 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.

I agree. The request-map is built in ring.adapter.jetty/proxy-handler
before being passed to the Jetty server. It would be nice to have a
function ring.adapter.jetty/proxy-servlet-aware-handler that can merge
the servlet keys to request map, so that it's easier to start the
Jetty server using that.
By servlet-context (application scope), I meant these:

http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletContext.html#setAttribute(java.lang.String,
java.lang.Object)

http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletContext.html#getAttribute(java.lang.String)

Regards,
Shantanu

James Reeves

unread,
Sep 5, 2010, 5:19:35 PM9/5/10
to ring-c...@googlegroups.com
On 5 September 2010 21:47, Shantanu Kumar <kumar.s...@gmail.com> wrote:
> I agree. The request-map is built in ring.adapter.jetty/proxy-handler
> before being passed to the Jetty server. It would be nice to have a
> function ring.adapter.jetty/proxy-servlet-aware-handler that can merge
> the servlet keys to request map, so that it's easier to start the
> Jetty server using that.

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

Shantanu Kumar

unread,
Sep 5, 2010, 6:35:52 PM9/5/10
to Ring


On Sep 6, 2:19 am, James Reeves <jree...@weavejester.com> wrote:
The primary reason is testing. I can start and stop Jetty during unit
tests using some context path. Though unit tests can run with an empty
string as context-path, but the integration tests usually have non-
empty strings for those.

http://jetty.codehaus.org/jetty/jetty-6/apidocs/org/mortbay/jetty/servlet/Context.html

Regards,
Shantanu

Shantanu Kumar

unread,
Sep 11, 2010, 6:26:32 AM9/11/10
to Ring
Okay, just a thought -- how about adding a key :servlet-keys (with a
default value false) to the options map that run-jetty accepts, which
if true should additionally include the servlet keys into the request
map.

Regards,
Shantanu
> http://jetty.codehaus.org/jetty/jetty-6/apidocs/org/mortbay/jetty/ser...
>
> Regards,
> Shantanu

Shantanu Kumar

unread,
Sep 11, 2010, 6:45:02 AM9/11/10
to Ring


On Sep 11, 3:26 pm, Shantanu Kumar <kumar.shant...@gmail.com> wrote:
> Okay, just a thought -- how about adding a key :servlet-keys (with a
> default value false) to the options map that run-jetty accepts, which
> if true should additionally include the servlet keys into the request
> map.

As a servlet is not guaranteed to be available during inline mode,
merging :servlet-request and :servlet-response should be enough.

James Reeves

unread,
Sep 11, 2010, 10:11:52 AM9/11/10
to ring-c...@googlegroups.com
On 5 September 2010 23:35, Shantanu Kumar <kumar.s...@gmail.com> wrote:
> The primary reason is testing. I can start and stop Jetty during unit
> tests using some context path. Though unit tests can run with an empty
> string as context-path, but the integration tests usually have non-
> empty strings for those.

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

Shantanu Kumar

unread,
Sep 19, 2010, 12:46:03 AM9/19/10
to Ring


On Sep 11, 7:11 pm, James Reeves <jree...@weavejester.com> wrote:
> On 5 September 2010 23:35, Shantanu Kumar <kumar.shant...@gmail.com> wrote:
>
> > The primary reason is testing. I can start and stop Jetty during unit
> > tests using some context path. Though unit tests can run with an empty
> > string as context-path, but the integration tests usually have non-
> > empty strings for those.
>
> 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 "".

I realized I was trying to find a servlet-oriented adapter in ring-
jetty-adapter, which it is not (for good reasons). Let me try and
explain why.

Some test environments (typically Java shops doing web dev) have this
notion of servlet-context-path that they manage (one context-path per
build) while deploying for tests. The interesting part is that context-
path is chosen at deploy-time as per test plan. Similar things happen
during production deployment. I am not sure if this is related to
Clout -- route URI template "/path/to/user/:id" should match both "/
some-context-path/path/to/user/9987" and "/path/to/user/9987"
irrespective of the context path at the time (not known in advance).

Running Jetty is useful when unit testing RESTful services. The Jetty
server can be started and stopped for each test case.

Secondly, the servlet-context object is used (as 'application scope')
by pre-existing Java code (such as servlet filters) that may be
required while doing mixed-Java-Clojure web development.

I am not sure if all this falls in the scope of ring-jetty-adapter,
but I thought it might be useful to share the information
nevertheless.

Regards,
Shantanu

James Reeves

unread,
Sep 19, 2010, 11:33:38 AM9/19/10
to ring-c...@googlegroups.com
On 19 September 2010 05:46, Shantanu Kumar <kumar.s...@gmail.com> wrote:
> Some test environments (typically Java shops doing web dev) have this
> notion of servlet-context-path that they manage (one context-path per
> build) while deploying for tests. The interesting part is that context-
> path is chosen at deploy-time as per test plan. Similar things happen
> during production deployment.

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

Shantanu Kumar

unread,
Sep 19, 2010, 4:25:04 PM9/19/10
to Ring
Thanks for replying in detail. I understand the handler-definition and
servlet-integration workflow much better now.

> > 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.

I should have explained. When I said unit tests I meant testing only
whether the routes are defined correctly, by mocking the route-handler
functions:

;; in web.clj
(defroutes app
  (POST "/book/:isbn" [isbn]
    (show-book isbn)))

;; in test_routes.clj
(def rest-url-prefix "http://localhost:8090")

(with-jetty-running {:port 8090}
(binding [web/show-book test-web/mock-show-book]
(let [response (make-request
(str rest-url-prefix
"/book/1934356336"))]
(verify-ok-response response
{:status (nmatcher 201)
:header (pmatcher :Location
(str rest-url-prefix "*"))} ))))

Such tests are useful to avoid regressions across API changes. System
tests also take care of payloads and versioning, whereas these unit
tests only verify the routes.

Regards,
Shantanu
Reply all
Reply to author
Forward
0 new messages