Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async handlers work only with Jetty #220

Open
dpiliouras opened this issue Jul 30, 2021 · 8 comments
Open

Async handlers work only with Jetty #220

dpiliouras opened this issue Jul 30, 2021 · 8 comments

Comments

@dpiliouras
Copy link
Contributor

dpiliouras commented Jul 30, 2021

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.

  1. The web.xml emitted should contain a async-supported tag - 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 :servlet tag in the make-web-xml function (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.

  2. The compile-listener function calls generate-handler in 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 async ring.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 by generate-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...

@weavejester
Copy link
Owner

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.

@dpiliouras
Copy link
Contributor Author

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.

dpiliouras added a commit to dpiliouras/lein-ring that referenced this issue Aug 13, 2021
@dpiliouras
Copy link
Contributor Author

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?

@weavejester
Copy link
Owner

I'd rather figure out why it doesn't work, but I'm not going to have any time to investigate for a while.

@dpiliouras
Copy link
Contributor Author

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}}

@weavejester
Copy link
Owner

Is it possible to provide a repository that replicates the error?

@dpiliouras
Copy link
Contributor Author

I'll see what I can do...Would a zipped project do, in case I'm not able to publish something online?

@weavejester
Copy link
Owner

Yes, if you like you can send it directly to my email, james@booleanknot.com. Otherwise, if the problem can be replicated with a minimal project, then that would be fine, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants