Compjure to WAR file?

122 views
Skip to first unread message

rob.blackwell

unread,
Dec 20, 2008, 6:48:29 PM12/20/08
to Compojure
I've built a simple prototype web app using Clojure and Compojure -
It's been
a very productive experience, despite the fact that I'm still a
Clojure
Newbie.

One of the things I like is that the web server is running under
Clojure, so any changes I make are instantly available without a big
recompile / redeploy loop.

Unfortunately, in the real world I'd probably need to compile to a WAR
file so that my client could host the app using a standard App server
in production.

Does anybody have a good solution for this? Must I move to something
like Webjure, or is there a way to keep the convenience of Compojure
for development?

What's the best practice / state-of-the-art approach for Clojure web
development right now?

Any suggestions much appreciated!

Adrian Cuthbertson

unread,
Dec 21, 2008, 12:18:31 PM12/21/08
to comp...@googlegroups.com
I've recently started using using compojure in a tomcat setup with good success. Thanks James, you're creating a great addition to facilitate clojure's use in prime time web application development!

The following are the basic steps I used for setting up compojure in a WAR based webapp on tomcat;
1.) Use the latest compojure git distribution.

2.) Create your servlet "library" file as follows in say ./src/myapp/MyServlet.clj;
(ns myapp.MyServlet
  (:use (compojure http html))
  (:import (java.io PrintWriter)
    (javax.servlet.http HttpServlet HttpServletRequest HttpServletResponse Cookie))
  (:gen-class
    :extends javax.servlet.http.HttpServlet))

(defservice ""    ; nb "" here allows class name as myapp.MyServlet class from genclass
  (GET "/"
    (html [:h1 "Hello compojure World"])])
  (GET "/setC"
    [(Cookie. "my-cookie" "1234")
    (html [:p "Ok, cookie has been set"])])
  (GET "/getC"
    (str "cookie:" (:my-cookie cookies)))
  (ANY "*"
    [404 (html [:h1 "Page Not Found"])]))

2.) Create  your WEB-INF directory as;
./WEB-INF/classes
./WEB-INF/lib
Copy compojure.jar, clojure-contrib.jar and clojure.jar to WEB-INF/lib

3.) Compile your clj file - here's an ANT build.xml that does this;
<project name="myapp" basedir="." default="clcompile">
<property name="build" value="WEB-INF/classes"/>
<property name="src" value="src"/>
<property name="lib" value="WEB-INF/lib"/>
<property name="CP" value="${build}:${lib}/compojure.jar:${lib}/clojure-contrib.jar:${lib}/clojure.jar:/opt/tomcat/common/lib/servlet-api.jar" />

 <target name="clcompile" >
    <java classname="clojure.lang.Compile">
      <classpath>
        <pathelement path="${CP}"/>
        <pathelement location="${src}"/>
      </classpath>
      <sysproperty key="clojure.compile.path" value="${build}"/>
      <arg value="myapp.MyServlet"/>
    </java>
</target>
</project>

4.) Create WEB-INF/web.xml as normal and containing;
<servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>myapp.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>myservlet</servlet-name><url-pattern>/*</url-pattern>
</servlet-mapping>

5.) You can then deploy the ./ directory to your app server either as a war file (using jar, perhaps in the above ant file), or by simply copying your WEB-INF dir to your tomcat (app server) context dir, e.g /opt/tomcat/webapps/myapp/WEB-INF.
In my case I create a webapp context for /myapp by adding a myapp.xml file to /opt/tomcat/conf/Catalina/localhost/myapp.xml, containing;
<Context path="/myapp" docBase="/opt/tomcat/webapps/myapp"
     debug="1" reloadable="true" crossContext="true" >
</Context>

6.) Restart your tomcat or reload your context and go to http://localhost:8080/myapp/ from your browser.

Final note;
I'm just now becoming familiar with James' html and http modules and the supporting functions for forms, pages, etc. This is REALLY looking like a great approach to developing webapps, seamlessly integrated with clojure and obviously other java backend libs. I'm looking forward to splicing it all into my production stuff and moving all new development to this platform.

James, you're welcome to use any of the above in your release notes/documentation. PS, +1 for your latest approach of having compojure as a lib and doing tools/framework separately. You can see from the above how nicely it now slots into a main-stream ("enterprisey") java web development setup.

Regards, Adrian.

Daniel E. Renfer

unread,
Dec 21, 2008, 4:01:45 PM12/21/08
to comp...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 12/21/2008 12:18 PM, Adrian Cuthbertson wrote:
> 2.) Create your servlet "library" file as follows in say
> ./src/myapp/MyServlet.clj;
> (ns myapp.MyServlet
> (:use (compojure http html))

> (:import (java.io <http://java.io> PrintWriter)


> (javax.servlet.http HttpServlet HttpServletRequest
> HttpServletResponse Cookie))
> (:gen-class
> :extends javax.servlet.http.HttpServlet))
>
> (defservice "" ; nb "" here allows class name as myapp.MyServlet
> class from genclass
> (GET "/"
> (html [:h1 "Hello compojure World"])])
> (GET "/setC"
> [(Cookie. "my-cookie" "1234")
> (html [:p "Ok, cookie has been set"])])
> (GET "/getC"
> (str "cookie:" (:my-cookie cookies)))
> (ANY "*"
> [404 (html [:h1 "Page Not Found"])]))
>


Thanks for this bit. I missed the part where defservice could use a
gen-classes class name. I haven't tested it myself, but I'm sure that'll
make my job so much easier.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAklOrq0ACgkQWorbjR01Cx5+lQCeNBVLps/UEOcbCj+IUpwf4Vaa
NvgAn0inTTARSSGotJ8NGCaQDqsFVTlJ
=YNUm
-----END PGP SIGNATURE-----

James Reeves

unread,
Dec 22, 2008, 11:52:10 AM12/22/08
to Compojure
On Dec 21, 5:18 pm, "Adrian Cuthbertson"
<adrian.cuthbert...@gmail.com> wrote:
> I'm just now becoming familiar with James' html and http modules and the
> supporting functions for forms, pages, etc. This is REALLY looking like a
> great approach to developing webapps, seamlessly integrated with clojure and
> obviously other java backend libs. I'm looking forward to splicing it all
> into my production stuff and moving all new development to this platform.

Thanks for the praise, and thank you for taking the time to produce
such a detailed guide! I'm using Compojure to develop an application
of my own, but as I'm still in the development stages, I haven't
investigated deploying to War files yet. A complete walkthrough like
this is very useful, and I'm sure will save people a lot of time.

> James, you're welcome to use any of the above in your release
> notes/documentation. PS, +1 for your latest approach of having compojure as
> a lib and doing tools/framework separately. You can see from the above how
> nicely it now slots into a main-stream ("enterprisey") java web development
> setup.

I tired to add this information to the github wiki, but the formatting
engine didn't seem to like the Clojure code, and there doesn't seem to
be a way to escape the offending characters. I'll have to look for
another place to store useful information like your above guide. I did
consider writing a Wiki in Compojure, but I haven't yet gotten around
to it :)

It also seems like the new genclass system in Clojure works with
functions called "-service" rather than "MyClass-service", so I think
I'll remove the prefix argument from defservice. Instead of writing
(defservice "" ...), you'll just need to write (defservice ...).

- James

Daniel Renfer

unread,
Dec 22, 2008, 1:35:56 PM12/22/08
to comp...@googlegroups.com
I'm having a little bit of trouble getting this to work for me. I keep
getting the same error:

java.lang.NullPointerException
java.util.regex.Matcher.getTextLength(Matcher.java:1140)
java.util.regex.Matcher.reset(Matcher.java:291)
java.util.regex.Matcher.<init>(Matcher.java:211)
java.util.regex.Pattern.matcher(Pattern.java:888)
clojure.core$re_matcher__4021.invoke(core.clj:2500)
compojure.http$match_route__362.invoke(http.clj:102)
compojure.http$apply_http_handler__406$route_QMARK___411.invoke(http.clj:225)
compojure.http$apply_http_handler__406$response_QMARK___414.invoke(http.clj:227)
clojure.core$some__3326.invoke(core.clj:1238)
compojure.http$apply_http_handler__406.invoke(http.clj:235)
compojure.http$http_service__484.invoke(http.clj:317)
net.mycyclopedia.Servlet$_service__104.invoke(Servlet.clj:22)
net.mycyclopedia.Servlet.service(Unknown Source)

My code is simple. Modified slightly for my setup

(ns net.mycyclopedia.Servlet
(:use (compojure html http))
(:import (javax.servlet.http HttpServletRequest
HttpServletResponse))
(:gen-class
:extends javax.servlet.http.HttpServlet))

(defservice ""
(ANY "/"
(html [:h1 "Hello World"]))
(ANY "/goodbye"
(html [:h1 "Goodbye World"])))


Any clues as to what may be going on?

Is it possible you could provide a complete example that I could work with?

James Reeves

unread,
Dec 22, 2008, 8:41:15 PM12/22/08
to Compojure
On Dec 22, 6:35 pm, "Daniel Renfer" <d...@kronkltd.net> wrote:
> I'm having a little bit of trouble getting this to work for me. I keep
> getting the same error:
>
> java.lang.NullPointerException
> ...
>
> Any clues as to what may be going on?

I suspect this is because Java doesn't appear to pass a path to the
servlet if you don't have a wildcard in the URL pattern of your
servlet mappings.

<servlet-name>myservlet</servlet-name><url-pattern>/</url-pattern>

So the above wouldn't work, because you're only matching "/", and
there is no path to pass to the servlet. You need to do:

<servlet-name>myservlet</servlet-name><url-pattern>/*</url-
pattern>

This is my best guess as to what the problem might be. I should
probably create a clearer error message for this.

- James

Daniel E. Renfer

unread,
Dec 22, 2008, 9:11:14 PM12/22/08
to comp...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

It's not an issue with my servlet mapping. I was using this setup before
without a problem when I was trying to write my own. (I didn't think
that defservlet would be appropriate for running under Tomcat)

As far as mappings in my defservice, I've tried with and without
wildcards and get the same error.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAklQSKoACgkQWorbjR01Cx7BsQCgzOa3je9t76POpGIQTGFPcexn
7PAAoJxsDYR0hcaikWbcT3WkBb3sUWN9
=v+KU
-----END PGP SIGNATURE-----

James Reeves

unread,
Dec 22, 2008, 9:29:57 PM12/22/08
to Compojure
On Dec 23, 2:11 am, "Daniel E. Renfer" <d...@kronkltd.net> wrote:
> As far as mappings in my defservice, I've tried with and without
> wildcards and get the same error.

Perhaps it's a problem with the beginning "/". Though it's unlikely to
work, you could try:

<servlet-name>myservlet</servlet-name><url-pattern>*</url-
pattern>

The root of the problem is that your web server isn't passing any path
information to the Compojure servlet. When the
HttpServletRequest.getPathInfo method is called, only null is
returned. I'm not certain why this would be, unless it's a
configuration problem.

- James

Daniel E. Renfer

unread,
Dec 22, 2008, 10:05:47 PM12/22/08
to comp...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Thanks, I was able to track down the problem.

On to the next challenge. :)


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAklQVX4ACgkQWorbjR01Cx7x5ACcDpAd3le+tdhQkjUUP/uD/GGL
qsUAoKcG2IIdURPDY1Xhn30jteiq7SsP
=GhNp
-----END PGP SIGNATURE-----

James Reeves

unread,
Dec 22, 2008, 10:06:53 PM12/22/08
to Compojure
On Dec 23, 3:05 am, "Daniel E. Renfer" <d...@kronkltd.net> wrote:
> Thanks, I was able to track down the problem.

Out of interest, what was the problem in the end?

- James

Daniel E. Renfer

unread,
Dec 22, 2008, 10:11:35 PM12/22/08
to comp...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

It turns out it was a problem with the servlet mapping after all. I'm
not sure what changed, because I know I was able to see the paths
before. (I was echoing them to the screen) Perhaps I changed it when I
was trying to figure out a way to essentially replicate what you had
done with Compojure in a gen-classed setting.

Glad I don't have to do that.


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAklQVucACgkQWorbjR01Cx6+LwCgvrEwwLiqQqh1rRxBDFT+a8Ex
FpUAoM0iSvMqeVnMg9MQ0ARqjl+Z6/lG
=CIhk
-----END PGP SIGNATURE-----

Adrian Cuthbertson

unread,
Dec 23, 2008, 12:09:03 AM12/23/08
to comp...@googlegroups.com
>I tired to add this information to the github wiki, but the formatting
>engine didn't seem to like the Clojure code, and there doesn't seem to
>be a way to escape the offending characters.

I haven't yet worked out a way to display clojure code in html format either. There have been one or two comments on the clojure forum related to doing it in wordpress though.


>It also seems like the new genclass system in Clojure works with
>functions called "-service" rather than "MyClass-service", so I think
>I'll remove the prefix argument from defservice. Instead of writing
>(defservice "" ...), you'll just need to write (defservice ...).

The one advantage of keeping the prefix, is that one could then have multiple servlet classes in the same library/package (clj file) by using multiple gen-class definitions. I.e you can have more than one gen-class and a different :prefix for each, then one defservice for each prefix. Obviously you'd also need an entry for earch in web.xml. I haven't tried it, but it should work. That could be useful for people who prefer to develop that way (and possibly for developing frameworks, etc).

Adrian.

Daniel Renfer

unread,
Dec 23, 2008, 8:13:22 AM12/23/08
to comp...@googlegroups.com
On Tue, Dec 23, 2008 at 12:09 AM, Adrian Cuthbertson
<adrian.cu...@gmail.com> wrote:
>>I tired to add this information to the github wiki, but the formatting
>>engine didn't seem to like the Clojure code, and there doesn't seem to
>>be a way to escape the offending characters.
>
> I haven't yet worked out a way to display clojure code in html format
> either. There have been one or two comments on the clojure forum related to
> doing it in wordpress though.
>
>>It also seems like the new genclass system in Clojure works with
>>functions called "-service" rather than "MyClass-service", so I think
>>I'll remove the prefix argument from defservice. Instead of writing
>>(defservice "" ...), you'll just need to write (defservice ...).
>
> The one advantage of keeping the prefix, is that one could then have
> multiple servlet classes in the same library/package (clj file) by using
> multiple gen-class definitions. I.e you can have more than one gen-class and
> a different :prefix for each, then one defservice for each prefix. Obviously
> you'd also need an entry for earch in web.xml. I haven't tried it, but it
> should work. That could be useful for people who prefer to develop that way
> (and possibly for developing frameworks, etc).
>
> Adrian.

What might be a better option is to drop the "-" off the generated
"service" name. Also accept nil as using "-", that way you're
basically emulating what genclass does.

(defservice nil ...) => (defn -service ...)
(defservice "-" ...) => (defn -service ...)
(defservice "foo" ...) => (defn fooservice) ;; :prefix "foo"
(defservice "foo-" ...) => (defn foo-service) ;; :prefix "foo-"

This would be a minor breaking change, if "" no longer translates to
"-service". It probably shouldn't, anyway, because you might have
want/need to use prefixes that don't include a "-".

James Reeves

unread,
Dec 28, 2008, 11:16:34 AM12/28/08
to Compojure
On Dec 23, 1:13 pm, "Daniel Renfer" <d...@kronkltd.net> wrote:
> What might be a better option is to drop the "-" off the generated
> "service" name. Also accept nil as using "-", that way you're
> basically emulating what genclass does.
>
> (defservice nil ...) => (defn -service ...)
> (defservice "-" ...) => (defn -service ...)
> (defservice "foo" ...) => (defn fooservice) ;; :prefix "foo"
> (defservice "foo-" ...) => (defn foo-service) ;; :prefix "foo-"

Instead of (defservice nil ...), it seems neater to just have
(defservice ...) as the default. That is, if the first argument is not
a string, then it assumes a default prefix of "-".

(defservice ...) => (defn -service ...)
(defservice "-" ...) => (defn -service ...)
(defservice "foo" ...) => (defn fooservice) ;; :prefix "foo"
(defservice "foo-" ...) => (defn foo-service) ;; :prefix "foo-"

Would that be satisfactory to all you war-file users?

- James

Daniel E. Renfer

unread,
Dec 28, 2008, 7:35:08 PM12/28/08
to comp...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Works for me.


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAklYGzAACgkQWorbjR01Cx5MSQCdFThY6isEPdUZMfWmdxEb9Xgu
R0YAoMFOanCIKyWgbnDk1jDBFAYX0VSM
=Q7CV
-----END PGP SIGNATURE-----

Adrian Cuthbertson

unread,
Dec 29, 2008, 12:29:40 AM12/29/08
to comp...@googlegroups.com
Sounds good to me too.

James Reeves

unread,
Jan 1, 2009, 8:54:16 AM1/1/09
to Compojure
On Dec 28 2008, 4:16 pm, James Reeves <weavejes...@googlemail.com>
wrote:
> Instead of (defservice nil ...), it seems neater to just have
> (defservice ...) as the default. That is, if the first argument is not
> a string, then it assumes a default prefix of "-".
>
> (defservice ...) => (defn -service ...)
> (defservice "-" ...) => (defn -service ...)
> (defservice "foo" ...) => (defn fooservice) ;; :prefix "foo"
> (defservice "foo-" ...) => (defn foo-service) ;; :prefix "foo-"

This change has now been committed to github. Note that this is a
minor breaking change, as (defservice "" ...) will now create a method
called service, and not -service.

- James

Mark Ryall

unread,
Jan 1, 2009, 8:56:30 PM1/1/09
to Compojure
This should work nicely on stax.net - has anyone already attempted
this out of interest?

On Dec 22 2008, 4:18 am, "Adrian Cuthbertson"
> value="${build}:${lib}/compojure.jar:${lib}/clojure-contrib.jar:${lib}/cloj ure.jar:/opt/tomcat/common/lib/servlet-api.jar"
> 6.) Restart your tomcat or reload your context and go tohttp://localhost:8080/myapp/from your browser.
>
> Final note;
> I'm just now becoming familiar with James' html and http modules and the
> supporting functions for forms, pages, etc. This is REALLY looking like a
> great approach to developing webapps, seamlessly integrated with clojure and
> obviously other java backend libs. I'm looking forward to splicing it all
> into my production stuff and moving all new development to this platform.
>
> James, you're welcome to use any of the above in your release
> notes/documentation. PS, +1 for your latest approach of having compojure as
> a lib and doing tools/framework separately. You can see from the above how
> nicely it now slots into a main-stream ("enterprisey") java web development
> setup.
>
> Regards, Adrian.
>

Mark Ryall

unread,
Jan 2, 2009, 8:07:23 PM1/2/09
to Compojure
In case anyone's curious - it did indeed work nicely on stax after a bit of tinkering with their ant script (from the basic servlet application template) and wrestling with my lack of familiarity with clojure and compojure.
Reply all
Reply to author
Forward
0 new messages