luminus-framework/luminus

+war profile: clojure.lang.ArityException: Wrong number of args (1) passed to: myapp.handler/app

nilskassube opened this issue · 21 comments

I created a new app by using

lein new luminus myapp +war

and created a WAR file using

lein uberwar

After deployment in a Tomcat 9.0.43 instance you get a 500 Internal Server Error with this error message:

clojure.lang.ArityException: Wrong number of args (1) passed to: myapp.handler/app
	clojure.lang.AFn.throwArity(AFn.java:429)
	clojure.lang.AFn.invoke(AFn.java:32)
	clojure.lang.Var.invoke(Var.java:384)
	myapp.listener$_contextInitialized$fn__11.invoke(listener.clj:1)
	ring.util.servlet$make_blocking_service_method$fn__98.invoke(servlet.clj:113)
	myapp.servlet$_service.invokeStatic(servlet.clj:1)
	myapp.servlet$_service.invoke(servlet.clj:1)
	myapp.servlet.service(Unknown Source)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

Looks like Tomcat is passing an argument to myapp.handler/app when trying to run the app. Could you try updating the handler to:

(defn app [_]
  (middleware/wrap-base #'app-routes))

and see if it runs?

Adding the _ argument prevents the 500 and the stack strace and yields a 200 with, unfortunately, empty content.

I'm seeing the same behavior locally. Looks like there is a bug here with the setup. The argument to the app handler is the request, and that's getting discarded.

To get the app to display the page you'll need to update the handler namespace as follows:

(mount/defstate app-handler :start (middleware/wrap-base #'app-routes))

(defn app []
  app-handler)

then in project.clj, you'll need to use the app-handler instead of app as the handler for the war:

:uberwar
  {:handler warapp.handler/app-handler
   :init warapp.handler/init
   :destroy warapp.handler/destroy
   :name "warapp.war"}

Finally, another problem is that if you're running with the servlet context then it ends up being injected in the route URI. So, if the app is deployed to tomcat at /myapp context that's part of the URI. That means you have to prefix all the context, .eg:

(defn home-routes []
  ["/myapp"
   {:middleware [middleware/wrap-csrf
                 middleware/wrap-formats]}
   ["/" {:get home-page}]
   ["/about" {:get about-page}]])

Aye, I was just about to report the same thing. Is this an issue I could submit a pull request on, or is it too dark and dirty for me to mess with? I can tell you that the arg that's passed in is a hashmap, and it's possible that it contains the config information I'm looking for but I haven't yet established that.

OK, I can see a problem here. I'm frequently deploying the exact same war file on different paths on the same server. If we have to prefix the path-name, I'm going to have either to pass it in as config or else to pull it out of the context somehow. It's quite likely that others will face the same issue.

...further investigation...

OK, what's passed in appears to be a Clojure map of the (first) request; it contains the key :servlet-context-path which in my case is bound to the token "/wartest", which is precisely the path name we need to prefix into home-routes. The key context i bound to the same token. Other interesting key bindings include:

  • :servlet-context #object[org.apache.catalina.core.ApplicationContextFacade 0x2a66fc6 "org.apache.catalina.core.ApplicationContextFacade@2a66fc6"]
  • :servlet #object[wartest.servlet 0x37f97b5b "wartest.servlet@37f97b5b"]
  • :servlet-request #object[org.apache.catalina.connector.RequestFacade 0x212a87b8 "org.apache.catalina.connector.RequestFacade@212a87b8"]

It looks as if we don't get this until the first request is made to the servlet. The fact that there's also a ServletResponse object bound in the map doesn't bother me since the javax.servlet.Servlet API passes both a request and a response object to the service method (and thus to the doGet, doPost, etc methods of HTTPServlet), so I'm assuming that at this stage the response object is not populated. I'm assuming that the :servlet-context-path in the first request will be identical to that in all subsequent requests, but I might be wrong there.

I made the changes you have suggested here manually, but I can't verify they work because of the other error you linked here that I submitted.

I've got some fixes I was testing that I could push out. I got the app working with Tomcat locally, so I'll try releasing that today. @simon-brooke I'll ping you once I get the changes pushed, and if you have other fixes or ideas PRs are always welcome. :)

I would be happy to try and contribute a PR, it's just that I'm still figuring this stuff out as I'm reading through the beta of the new edition of your book.

first try: copied servlet-api.jar from the Tomcat /lib directory into the app's WEB-INF/lib dir. Doesn't seem to make a difference

Generally, most people tend to run an embedded HTTP server nowadays, so the war options hasn't received much attention for a while.

ok, so I pushed some fixes for the +war profile here, you can try it locally by cloning the repo and running lein install from it to publish the template on your machine and then running lein new myapp +war to generate the project.

I tested it locally with Tomcat 9 and seems to be working here.

These changes do seem to address the arity problem, but they immediately lead me to the next set of issues: the paths to the scripts are absolute. I manually changed those so they load, but they contain links that also don't understand they are not at the root of the server. Right now, that is turning into 404s when asking for base.js, deps.js, and cljs_deps.js. Not sure how many layers of the onion there are to peel back here.

There's no good way to handle servlet context aside from manually specifying it unfortunately. It's a hack that Java app servers use and not part of HTTP spec. Any paths requested from the server by the browser need to contain the context.

Fair enough, but how would I find all the places that need to be manually changed?

That would be anything that's defined on the HTML template. However, you shouldn't be seeing requests for base.js, deps.js, and cljs_deps.js if you're compiling ClojureScript with advanced minification. That should produce a single Js artifact that you request. I wouldn't recommend running the app on Tomcat in development mode, and instead just use lein run to start it with its own embedded server.

Good point! I set ':optimizations :advanced', but now I'm getting sidetracked by an error that appears to be because I'm on Windows:

java.nio.file.InvalidPathException: Illegal char <:> at index 2: /C:/Users/myuser/src/myproject/target/cljsbuild/public/js/cljs/core.js

Any idea if that slash being put there by code in lein-uberwar or something else?

unfortunately can't help there as I don't have windows here :)

Looks like this works for me: I got the code on to a Linux machine, compiled with advanced optimizations, and it loads in Tomcat now. So thank you very much! I seem to have a new issue where Luminus is using ring-proxy to pass requests back and for between the browser and a server but something very strange is happening where it's a chunked response, but the browser also sees a Content-Length header set. But I haven't been able to track down whether that has anything to do with Luminus or is a Tomcat issue.

Good to hear things are mostly working, and that particular issue sounds like it might be specific to ring-proxy. The headers that get set would be independent of Tomcat.