basic servlet setup

114 views
Skip to first unread message

Wilson MacGyver

unread,
Jul 24, 2010, 11:01:54 PM7/24/10
to comp...@googlegroups.com
Hi,

I thought I give the current compojure+ring another look to see where
things stand. I'm setting up
a servlet because I want to produce a war file so that it can be
deployed to tomcat etc.

So I wrote something extremely basic.

(ns mywebapp.firstservlet
(:use [ring.util.servlet :only (defservice)])
(:use compojure.core)
(:gen-class
:extends javax.servlet.http.HttpServlet))

(defroutes app
(GET "/working" [] "it worked")
(GET "/sayhi" [] "say hi")
(GET "/" [] "top level"))


(defservice app)


I also created a web.xml file to map url-pattern / to it.

when I deploy it to jetty, going to any url gives me a
java.lang.NullPointerException: Handler returned nil
at ring.util.servlet$make_service_method$fn__78.invoke(servlet.clj:118)


if I change the defroute above to
(defroutes app
(GET "/working" [] "it worked")
(GET "/sayhi" [] "say hi")
(GET "/*" [] "top level"))

it works, but no matter what I do, it always says "top level". I
thought the route are
matched in the order it was defined?

any input on what I'm missing is appreciated.

Thanks,

--
Omnem crede diem tibi diluxisse supremum.

James Reeves

unread,
Jul 25, 2010, 4:37:59 AM7/25/10
to comp...@googlegroups.com

In web.xml, you need to bind the url pattern "/*", not "/". Without the wildcard, it will only match the root.

- James


--
You received this message because you are subscribed to the Google Groups "Compojure" group.
To post to this group, send email to comp...@googlegroups.com.
To unsubscribe from this group, send email to compojure+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/compojure?hl=en.

Jeroen Vloothuis

unread,
Jul 25, 2010, 6:31:27 AM7/25/10
to comp...@googlegroups.com
On Sun, Jul 25, 2010 at 5:01 AM, Wilson MacGyver <wmac...@gmail.com> wrote:
I thought I give the current compojure+ring another look to see where
things stand. I'm setting up
a servlet because I want to produce a war file so that it can be
deployed to tomcat etc.

I just wrote a post on my web-log which explains my setup for doing this. You might find it interesting if, like me, you're not that familiar with the Java ecosystem.

http://jeroenvloothuis.blogspot.com/2010/07/deploying-clojure-web-application.html

Feedback is appreciated. Especially If anything is unclear / wrong

--
Jeroen

Wilson MacGyver

unread,
Jul 25, 2010, 9:32:36 AM7/25/10
to comp...@googlegroups.com
I've tried that as well before posing the question. using the following

<web-app>
<servlet>
<servlet-name>firstservlet</servlet-name>
<servlet-class>mywebapp.firstservlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstservlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

still get the same result as I stated before. using the web.xml from above.

redoing the defroute with a catch all, I have

(ns mywebapp.firstservlet
(:use [ring.util.servlet :only (defservice)])
(:use compojure.core)
(:gen-class
:extends javax.servlet.http.HttpServlet))

(defroutes app
(GET "/working" [] "it worked")
(GET "/sayhi" [] "say hi")
(GET "/" [] "top level")

(ANY "/*" [] "not found"))


(defservice app)

it always returns "not found", and never match any of the other url
pattern. I checked
the defroutes syntax against docs/examples I found on the web several times.
It looks right to me. But for some reason, it's always matching the last one.

Wilson MacGyver

unread,
Jul 25, 2010, 9:36:31 AM7/25/10
to comp...@googlegroups.com
thanks, in my case, I didn't have issue producing the war file or
getting jetty started.

I'm using gradle and the clojure plugin clojuresque, as well as the jetty
and war plugin for gradle.

so I can just do gradle war to produce the war file.
I can do gradle jettyRun to run jetty etc.

> --
> You received this message because you are subscribed to the Google Groups
> "Compojure" group.
> To post to this group, send email to comp...@googlegroups.com.
> To unsubscribe from this group, send email to
> compojure+...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/compojure?hl=en.
>

--

Saul Hazledine

unread,
Jul 25, 2010, 5:24:18 PM7/25/10
to Compojure
Hello,
On Jul 25, 5:01 am, Wilson MacGyver <wmacgy...@gmail.com> wrote:
>
>
> (defroutes app
>              (GET "/working" [] "it worked")
>              (GET "/sayhi" [] "say hi")
>              (GET "/" [] "top level"))
>
> (defservice app)
>
> I also created a web.xml file to map url-pattern / to it.
>
> when I deploy it to jetty, going to any url gives me a
> java.lang.NullPointerException: Handler returned nil
>         at ring.util.servlet$make_service_method$fn__78.invoke(servlet.clj:118)
>

This might not be your problem but one thing that usually bites me in
the ass is that Compojure will match to the WHOLE path not just path
exposed to the servlet. So if your web container puts myservlet on the
path /myservlet you need to match to:

(GET "/myservlet/sayhi" "say hi")

Saul

James Reeves

unread,
Jul 25, 2010, 5:27:42 PM7/25/10
to comp...@googlegroups.com
Try putting in a catch-all route like this:

(ANY "*" {uri :uri} uri)

Compojure matches on the :uri key of the request, and this route echos
the URI in the response. This might tell you why the URI isn't
matching in this case.

- James

Wilson MacGyver

unread,
Jul 25, 2010, 6:13:20 PM7/25/10
to comp...@googlegroups.com
that was it! thank you very much. I had to change
my defroutes to

(defroutes app
(GET "/webexample/working" [] "it worked")
(GET "/webexample/sayhi" [] "say hi")
(GET "/webexample/" [] "top level")


(ANY "/*" [] "not found"))

because my working directory is webexample, and gradle
package up the war to webexample.war by convention,
thus deploying to to jetty as /webexample in the webexample
context

I didn't realize compojure route mapping takes web context into
account.

James, is there a way to change this behavior? Because
unless I deploy the war to ROOT context, it would never match.
and since context changes depends on what you name the war file.
it'd be nice if this can be configurable somehow?

> --
> You received this message because you are subscribed to the Google Groups "Compojure" group.
> To post to this group, send email to comp...@googlegroups.com.
> To unsubscribe from this group, send email to compojure+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/compojure?hl=en.
>
>

--

Saul Hazledine

unread,
Jul 26, 2010, 5:31:18 AM7/26/10
to Compojure
On Jul 25, 11:13 pm, Wilson MacGyver <wmacgy...@gmail.com> wrote:
> that was it! thank you very much. I had to change
> my defroutes to
>
> (defroutes app
>              (GET "/webexample/working" [] "it worked")
>              (GET "/webexample/sayhi" [] "say hi")
>              (GET "/webexample/" [] "top level")
>              (ANY "/*" [] "not found"))
>
> because my working directory is webexample, and gradle
> package up the war to webexample.war by convention,
> thus deploying to to jetty as /webexample in the webexample
> context
>
> I didn't realize compojure route mapping takes web context into
> account.
>
I don't know how easy it is to change this behaviour because it relies
on a call to
HttpServletRequest.getPathInfo()
which is only available when you're running as a servlet - ring and
compojure will run a web app as a standalone application which seems
to be the preferred method for most people. Thats also how it runs
during a swank session which is something I find very useful.

When on a live server I always run Jetty behind an Nginx proxy and use
Nginx to cache static content and to translate paths. This way a
servlet running in Jetty is always be on the same absolute path. Any
Compojure based servlet can share Jetty with lots of other servlets
(written in Java or whatever) and Nginx does the path conversion. Its
also much easier to change an Nginx configuration file than to edit a
web.xml and create a new war file if you do want to change a path.
With Nginx you also get control over virtual hosts etc.

e.g
URI / -> Nginx proxies to http://localhost:8080/myservlet/ -> Jetty
gets /myservlet/
URI /hi -> Nginx proxies to http://localhost:8080/myservlet/hi ->
Jetty gets /myservlet/hi
URI /static/hi.css -> Nginx caches http://localhost:8080/myservlet/hi/static/hi.css

This will work for most frontend web servers but I personally like
Nginx because its lightweight and easy to configure.

One downside with this method is that all absolute paths in your
application must be generated by a function that is aware of whether
it is running behind a frontend server or as a standalone application
during development - however this is a good practice to follow anyway.


Saul

James Reeves

unread,
Jul 26, 2010, 5:43:34 AM7/26/10
to comp...@googlegroups.com
On 25 July 2010 23:13, Wilson MacGyver <wmac...@gmail.com> wrote:
> James, is there a way to change this behavior? Because
> unless I deploy the war to ROOT context, it would never match.
> and since context changes depends on what you name the war file.
> it'd be nice if this can be configurable somehow?

Yes, you can use some middleware that alters the :uri key:

(defn wrap-context [handler context]
(fn [{uri :uri :as request}]
(if (.startsWith uri context)
(handler (assoc request :uri (.substring uri (.length context)))))))

I'm thinking about adding a function like this into Ring. I'm also
going to add an option or some middleware to defservice and defservlet
that would make servlets take the context into account.

- James

Wilson MacGyver

unread,
Jul 26, 2010, 9:41:54 AM7/26/10
to comp...@googlegroups.com
Thank you. Adding this would be a good idea. Since it's a common usage
If you deploy multiple wars to a single app server. Which is a pretty common situation.

Wilson MacGyver

unread,
Jul 26, 2010, 9:53:28 AM7/26/10
to comp...@googlegroups.com
The problem with this is, if you have multiple wars in a app server.
This becomes
very tedious. Our production tomcat servers regularly have 20-30 wars deployed
under different context. And on our dev servers even more.

On Mon, Jul 26, 2010 at 5:31 AM, Saul Hazledine <sha...@gmail.com> wrote:
...


> When on a live server I always run Jetty behind an Nginx proxy and use
> Nginx to cache static content and to translate paths. This way a
> servlet running in Jetty is always be on the same absolute path. Any
> Compojure based servlet can share Jetty with lots of other servlets
> (written in Java or whatever) and Nginx does the path conversion. Its
> also much easier to change an Nginx configuration file than to edit a
> web.xml and create a new war file if you do want to change a path.
> With Nginx you also get control over virtual hosts etc.
>
> e.g
> URI / -> Nginx proxies to http://localhost:8080/myservlet/ -> Jetty
> gets /myservlet/
> URI /hi -> Nginx proxies to http://localhost:8080/myservlet/hi ->
> Jetty gets /myservlet/hi
> URI /static/hi.css -> Nginx caches http://localhost:8080/myservlet/hi/static/hi.css
>

--

Wilson MacGyver

unread,
Jul 26, 2010, 10:11:19 AM7/26/10
to comp...@googlegroups.com
Does context already exist in a binding within ring/compojure?
or do I have to use java interop to do
(.getServletContext ...)

Thanks

On Mon, Jul 26, 2010 at 5:43 AM, James Reeves <jre...@weavejester.com> wrote:
> (defn wrap-context [handler context]
>  (fn [{uri :uri :as request}]
>    (if (.startsWith uri context)
>      (handler (assoc request :uri (.substring uri (.length context)))))))

--

James Reeves

unread,
Jul 26, 2010, 10:30:12 AM7/26/10
to comp...@googlegroups.com
On 26 July 2010 15:11, Wilson MacGyver <wmac...@gmail.com> wrote:
> Does context already exist in a binding within ring/compojure?
> or do I have to use java interop to do
> (.getServletContext ...)

A Ring request translated from a servlet request will have, I believe,
a :servlet-request key that references a HttpServletRequest object.
You can probably use this to pull out the truncated path (getPathInfo,
possibly?). You can then put this path into :uri, and move the
existing URI value to a :full-uri key.

I'm planning to create middleware like this anyway, so if you happen
to do this before me, I'll be happy to merge it into Ring core.

- James

Reply all
Reply to author
Forward
0 new messages