luminus-framework / luminus

documentation site for Luminus framework
http://www.luminusweb.net/
629 stars 121 forks source link

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

Open nilskassube opened 3 years ago

nilskassube commented 3 years ago

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)
yogthos commented 3 years ago

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?

nilskassube commented 3 years ago

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

yogthos commented 3 years ago

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.

yogthos commented 3 years ago

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}]])
simon-brooke commented 3 years ago

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.

simon-brooke commented 3 years ago

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.

simon-brooke commented 3 years ago

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

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.

bigfoote commented 3 years ago

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.

yogthos commented 3 years ago

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

bigfoote commented 3 years ago

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.

bigfoote commented 3 years ago

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

yogthos commented 3 years ago

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

yogthos commented 3 years ago

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.

bigfoote commented 3 years ago

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.

yogthos commented 3 years ago

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.

bigfoote commented 3 years ago

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

yogthos commented 3 years ago

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.

bigfoote commented 3 years ago

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?

yogthos commented 3 years ago

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

bigfoote commented 3 years ago

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.

yogthos commented 3 years ago

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.