Async handlers work only with Jetty
There are two issues preventing async handlers from working in any Servlet container, other than Jetty (e.g. Tomcat). Thankfully, one of them is super easy to fix, but the other I'm having trouble with and I would appreciate your help.
-
The
web.xmlemitted should contain aasync-supportedtag - especially when the[:ring :async?]option is true. This is very easily fixable by adding[:async-supported (:async? ring-options false)]], right before closing the:servlettag in themake-web-xmlfunction (here). If I manage to fix point 2 (see below), I can include this in the PR, otherwise it's such a trivial change that I don't really see the point of a PR just for that. -
The
compile-listenerfunction callsgenerate-handlerin order to resolve the actual user handler(here). As you can see if the[:ring :servlet-path-info?]option is true (or missing), the user-handler is wrapped in another function, which however takes a single argument, and therefore won't work with the three arguments passed to it (per the asyncring.util.servlet/make-service-method). If the[:ring :servlet-path-info?]option is explicitly set to false, then it works as expected (assuming of course that the actual handler allows for 3 args). Now, you might be tempted to think that this is also easily fixable, but trust me, it isn't...I tried adding a 3-arg arity to that function returned bygenerate-handler, and everything compiles/builds/deploys no problem, but then when I try to use it on a real project, I get compile-syntax errors like this:
:clojure.main/message
"Syntax error (ClassNotFoundException) compiling at (com/foo/gw/listener.clj:1:519).\nleiningen.ring.war\n",
:clojure.main/triage
{:clojure.error/phase :compile-syntax-check,
:clojure.error/line 1,
:clojure.error/column 519,
:clojure.error/source "listener.clj",
:clojure.error/path "com/foo/gw/listener.clj",
:clojure.error/class java.lang.ClassNotFoundException,
:clojure.error/cause "leiningen.ring.war"},
Here is the code in case you have doubts that the syntax is correct:
(defn- with-context-path-info [req context-path]
(assoc req
:context context-path
:path-info (-> (:uri req) (subs (count context-path)) not-empty (or "/"))))
(defn generate-handler [project handler-sym]
(if (get-in project [:ring :servlet-path-info?] true)
`(let [handler# ~(generate-resolve handler-sym)]
(fn
([request# respond# raise#]
(let [context# (.getContextPath
^javax.servlet.http.HttpServletRequest
(:servlet-request request#))]
(-> request#
(with-context-path-info context#)
(handler# respond# raise#))))
([request#]
(let [context# (.getContextPath
^javax.servlet.http.HttpServletRequest
(:servlet-request request#))]
(-> request#
(with-context-path-info context#)
(handler#))))))
(generate-resolve handler-sym)))
I also tried generating two distinct functions based on the :async? option, but I got the exact same compile errors:
(if (get-in project [:ring :async?])
(fn [req respond raise] ...)
(fn [req] ...))
If you can provide any form of insight as to why such a simple change (adding an arity) would cause compile-syntax errors, that would be fantastic. Many thanks in advance...
I'm not sure if this is the only error, but the problem I can see is that you're using with-context-path-info inside the code you generate, but that function is private, not public.
Hmm fair point, but I would expect a IllegalStateException - var: #'lein.ring.war/with-context-path-info is not public, if that was really the issue here...I'll try it and let you know.
Hi there,
So, I've abandoned the idea of providing 2 arities, and I've gone with var-args instead which seems to work:
(defn generate-handler [project handler-sym]
(if (get-in project [:ring :servlet-path-info?] true)
`(let [handler# ~(generate-resolve handler-sym)]
(fn [req# & args#]
(let [context# (.getContextPath
^javax.servlet.http.HttpServletRequest
(:servlet-request req#))]
(apply handler#
(assoc req#
:context context#
:path-info (-> (:uri req#) (subs (count context#)) not-empty (or "/")))
args#))))
(generate-resolve handler-sym)))
I have to admit that I'm not 100% happy with it, but at least it works. Do you want a PR for it, or would you prefer to have a stab at it yourself - see if you can get the 2-arity working?
I'd rather figure out why it doesn't work, but I'm not going to have any time to investigate for a while.
Like you, I would love to know why it can't compile the 2-arity fn, but at this point I've run out of ideas and things I can try. My custom fork is at version 0.12.23 (with the above code which at least works), so that gives you an idea of how many attempts I've made. The error is not helpful at all :( ...
$ cat /var/folders/zw/c3k5myj97z740h4sqdmnvwsr0000gq/T/clojure-8932508282312467787.edn
{:clojure.main/message
"Syntax error (ClassNotFoundException) compiling at (com/elcom/gw/listener.clj:1:808).\nleiningen.ring.war\n",
:clojure.main/triage
{:clojure.error/phase :compile-syntax-check,
:clojure.error/line 1,
:clojure.error/column 808,
:clojure.error/source "listener.clj",
:clojure.error/path "com/elcom/gw/listener.clj",
:clojure.error/class java.lang.ClassNotFoundException,
:clojure.error/cause "leiningen.ring.war"},
:clojure.main/trace
{:via
[{:type clojure.lang.Compiler$CompilerException,
:message
"Syntax error compiling at (com/elcom/gw/listener.clj:1:808).",
:data
{:clojure.error/phase :compile-syntax-check,
:clojure.error/line 1,
:clojure.error/column 808,
:clojure.error/source "com/elcom/gw/listener.clj"},
:at [clojure.lang.Compiler analyzeSeq "Compiler.java" 7119]}
{:type java.lang.ClassNotFoundException,
:message "leiningen.ring.war",
:at
[java.net.URLClassLoader findClass "URLClassLoader.java" 435]}],
:trace
[[java.net.URLClassLoader findClass "URLClassLoader.java" 435]
[clojure.lang.DynamicClassLoader
findClass
"DynamicClassLoader.java"
69]
[java.lang.ClassLoader loadClass "ClassLoader.java" 589]
[clojure.lang.DynamicClassLoader
loadClass
"DynamicClassLoader.java"
77]
[java.lang.ClassLoader loadClass "ClassLoader.java" 522]
[java.lang.Class forName0 "Class.java" -2]
[java.lang.Class forName "Class.java" 468]
[clojure.lang.RT classForName "RT.java" 2212]
[clojure.lang.RT classForNameNonLoading "RT.java" 2225]
[clojure.lang.Compiler$HostExpr maybeClass "Compiler.java" 1041]
[clojure.lang.Compiler macroexpand1 "Compiler.java" 7049]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7097]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3892]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7113]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6124]
[clojure.lang.Compiler$LetExpr$Parser parse "Compiler.java" 6440]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7111]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6124]
[clojure.lang.Compiler$FnMethod parse "Compiler.java" 5471]
[clojure.lang.Compiler$FnExpr parse "Compiler.java" 4033]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6124]
[clojure.lang.Compiler$LetExpr$Parser parse "Compiler.java" 6440]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7111]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler access$300 "Compiler.java" 38]
[clojure.lang.Compiler$LetExpr$Parser parse "Compiler.java" 6388]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7111]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6124]
[clojure.lang.Compiler$FnMethod parse "Compiler.java" 5471]
[clojure.lang.Compiler$FnExpr parse "Compiler.java" 4033]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7099]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler access$300 "Compiler.java" 38]
[clojure.lang.Compiler$DefExpr$Parser parse "Compiler.java" 596]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7111]
[clojure.lang.Compiler analyze "Compiler.java" 6793]
[clojure.lang.Compiler analyze "Compiler.java" 6749]
[clojure.lang.Compiler compile1 "Compiler.java" 7730]
[clojure.lang.Compiler compile1 "Compiler.java" 7725]
[clojure.lang.Compiler compile1 "Compiler.java" 7725]
[clojure.lang.Compiler compile "Compiler.java" 7802]
[clojure.lang.RT compile "RT.java" 411]
[clojure.lang.RT load "RT.java" 457]
[clojure.lang.RT load "RT.java" 424]
[clojure.core$load$fn__6856 invoke "core.clj" 6115]
[clojure.core$load invokeStatic "core.clj" 6114]
[clojure.core$load doInvoke "core.clj" 6098]
[clojure.lang.RestFn invoke "RestFn.java" 408]
[clojure.core$load_one invokeStatic "core.clj" 5897]
[clojure.core$compile$fn__6861 invoke "core.clj" 6125]
[clojure.core$compile invokeStatic "core.clj" 6125]
[user$eval5 invokeStatic "form-init10924602446184730501.clj" 1]
[user$eval5 invoke "form-init10924602446184730501.clj" 1]
[clojure.lang.Compiler eval "Compiler.java" 7181]
[clojure.lang.Compiler eval "Compiler.java" 7170]
[clojure.lang.Compiler eval "Compiler.java" 7171]
[clojure.lang.Compiler load "Compiler.java" 7640]
[clojure.lang.Compiler loadFile "Compiler.java" 7578]
[clojure.main$load_script invokeStatic "main.clj" 475]
[clojure.main$init_opt invokeStatic "main.clj" 477]
[clojure.main$init_opt invoke "main.clj" 477]
[clojure.main$initialize invokeStatic "main.clj" 508]
[clojure.main$null_opt invokeStatic "main.clj" 542]
[clojure.main$null_opt invoke "main.clj" 539]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]],
:cause "leiningen.ring.war",
:phase :compile-syntax-check}}
Is it possible to provide a repository that replicates the error?
I'll see what I can do...Would a zipped project do, in case I'm not able to publish something online?
Yes, if you like you can send it directly to my email, [email protected]. Otherwise, if the problem can be replicated with a minimal project, then that would be fine, too.