5

I have a Ring app that through deploys to production as an uberwar thing; myservice.war. In production the WAR file gets tossed into Jetty where it runs in a context that follows its name

$ curl -i -X GET http://myservice.qa1.example.com:8080/myservice/healthz
HTTP/1.1 200 OK
...

When I run locally through lein ring, I need it to run in the same context; myservice.

$lein ring server-headless
2015-10-14 14:04:03,457  level=INFO [main] Server:271 - jetty-7.6.13.v20130916
2015-10-14 14:04:03,482  level=INFO [main] AbstractConnector:338 - Started SelectChannelConnector@0.0.0.0:10313
Started server on port 10313

But the same curl goes all 404 on me locally.

$ curl -i -X GET http://localhost:10313/myservice/healthz
HTTP/1.1 404 Not Found
...

The lein ring thing deployed it onto the root context.

$ curl -i -X GET http://localhost:10313/healthz
HTTP/1.1 200 OK
...

Whats up with that? How do I direct lein ring to deploy into a context name of my choosing? I need curl -i -X GET http://localhost:10313/myservice/healthz to work from lein ring

Bob Kuhar
  • 10,838
  • 11
  • 62
  • 115

1 Answers1

1

One way to work around this issue is to create the second (standalone) set of routes for your app. You also create the second handler for the standalone case. Then you can use Leiningen profiles to specify different handlers for the standalone case and the uberwar case. The default profile is used when running the app standalone. The :uberjar profile is used when the uberwar is created. As a result, your standalone handler going to be used with lein ring server-headless and your regular handler is going to be used when the war is deployed into a container.

Not much additional code needed to create the second set of routes. You can just wrap the existing routes in a context of your choosing. Suppose the following are your routes and ring handler:

(defroutes app-routes
  (GET "/healthz" [] "Hello World")
  (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

Additional routes and handler for the standalone case would look like this:

(defroutes standalone-routes
  (context "/myservice" req app-routes)
  (route/not-found "Not Found"))

(def standalone-app
  (wrap-defaults standalone-routes site-defaults))

Now, onto lein-ring configuration in project.clj. We want the default ring handler to point to standalone-app. The ring handler for the uberwar should point to app. The :ring entry in the project map in project.clj should look like this (adjust for your actual namespace):

:ring {:handler myservice.handler/standalone-app}

Also, merge the following into your :profiles map in project.clj:

:uberjar {:ring {:handler myservice.handler/app}}

Please be sure to use the latest version of the lein-ring plugin. Version 0.9.7 worked for me. Earlier versions, like 0.8.3, did not work because they did not use the :uberjar profile when running the uberwar task.

If you do all this, and assuming your war file is called myservice.war, the context part of the URI is going to be the same whether your app is started with lein ring server-headless or if the war file is deployed in Jetty.

$ curl http://localhost:[port]/myservice/healthz
ez121sl
  • 2,371
  • 1
  • 21
  • 28
  • Oh my. Excellent answer. Tomorrow I'll see if it works out for me. – Bob Kuhar Oct 16 '15 at 03:05
  • Hmmm. I can see what it is trying to do, but when I run in Jetty its always Not Found for /myservice/healthz. This solution really is clever, but having to explain the 2 different handlers in :ring vs :uberjar is going to be a tough sell in code reviews. This seems a gap to me in, what? lein-ring? – Bob Kuhar Oct 19 '15 at 06:38
  • The "Not Found" response you see may be due to the uberjar profile not kicking in when building the uberwar. This could be due to an old version of the `lein-ring` plugin or some typo. If `/myservice/myservice/healthz` works, then that's probably the issue. – ez121sl Oct 19 '15 at 14:04
  • Another approach altogether maybe to rename your war file to `root.war`. If you deploy that in Jetty, the app will go into the root context, not into `/myservice`. If you are going this route, undo everything suggested in this answer. – ez121sl Oct 19 '15 at 14:07
  • A lot of production web apps feature a front-end web server / load balancers like nginx or Apache. When those things are in use, you can configure URL rewriting every which way you need. – ez121sl Oct 19 '15 at 14:14
  • This works great. My disconnect was "merge the following into :profiles". Once I did that correctly, it works as described. Working project is here: https://github.com/robertkuhar/myservice – Bob Kuhar Oct 19 '15 at 19:09