tailrecursion / boot

A Clojure build tool
Eclipse Public License 1.0
111 stars 18 forks source link

embedded tomcat fails to work with boot 2 #32

Closed jumblerg closed 10 years ago

jumblerg commented 10 years ago

to reproduce, create a project with the following build script:

(set-env!
  :src-paths    #{"src"}
  :tgt-path     "tgt"
  :dependencies '[[org.apache.tomcat.embed/tomcat-embed-jasper       "8.0.8"]
                  [org.apache.tomcat.embed/tomcat-embed-logging-juli "8.0.8"]
                  [org.apache.tomcat.embed/tomcat-embed-core         "8.0.8"] ])

(task-options!
  watch   [:quiet       false]
  speak   [:theme       "ordinance"]
  pom     [:project     'tailrecursion/boot.worker.tomcat
           :version      "0.1.0-SNAPSHOT"
           :description  "Boot worker to create a standalone Tomcat server."
           :url          "http://github.com/tailrecursion/boot.task.tomcat"
           :scm         {:url  "https://github.com/tailrecursion/boot.task.tomcat"}
           :license     {:name "Eclipse Public License"
                         :url  "http://www.eclipse.org/legal/epl-v10.html"} ])

then launch the repl:

boot.user=> (import 'org.apache.catalina.startup.Tomcat)
org.apache.catalina.startup.Tomcat
boot.user=> (require '[clojure.java.io :as io])
nil
boot.user=> (defn create [dir war port]
       #_=>   (.mkdirs (io/file dir "webapps"))
       #_=>   (doto (Tomcat.)
       #_=>     (.setBaseDir dir)
       #_=>     (.setPort port)
       #_=>     (.addWebapp "" war)
       #_=>     (.start) ))
#'boot.user/create
boot.user=> (def server (create "basedir" "my.war" 8888))
Sep 11, 2014 1:06:02 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8888"]
Sep 11, 2014 1:06:02 AM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFO: Using a shared selector for servlet write/read
Sep 11, 2014 1:06:02 AM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
Sep 11, 2014 1:06:02 AM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/8.0.8
Sep 11, 2014 1:06:03 AM org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
INFO: No global web.xml found
Sep 11, 2014 1:06:03 AM org.apache.tomcat.util.digester.Digester endElement
SEVERE: End event threw exception
java.lang.ClassNotFoundException: org.apache.tomcat.util.descriptor.web.ServletDef
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1333)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1180)
    at org.apache.tomcat.util.IntrospectionUtils.callMethod1(IntrospectionUtils.java:355)
    at org.apache.tomcat.util.digester.SetNextRule.end(SetNextRule.java:145)
    at org.apache.tomcat.util.digester.Digester.endElement(Digester.java:959)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:606)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1786)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2951)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:846)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:775)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1210)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:628)
    at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1457)
    at org.apache.tomcat.util.descriptor.web.WebXmlParser.parseWebXml(WebXmlParser.java:120)
    at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1104)
    at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:768)
    at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:303)
    at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
    at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5069)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
jumblerg commented 10 years ago

this same code worked fine with boot 1; it also works using the lein repl.

create a project.clj

(defproject tomcatlein "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.apache.tomcat.embed/tomcat-embed-jasper       "8.0.8"]
                [org.apache.tomcat.embed/tomcat-embed-logging-juli "8.0.8"]
                [org.apache.tomcat.embed/tomcat-embed-core         "8.0.8" ]])

and launch the repl:

stratus:tomcatlein jumblerg$ lein repl
nREPL server started on port 54809 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (import 'org.apache.catalina.startup.Tomcat)
org.apache.catalina.startup.Tomcat
user=> (require '[clojure.java.io :as io])
nil
user=> (defn create [dir war port]
  #_=>   (.mkdirs (io/file dir "webapps"))
  #_=>   (doto (Tomcat.)
  #_=>     (.setBaseDir dir)
  #_=>     (.setPort port)
  #_=>     (.addWebapp "" war)
  #_=>     (.start) ))
#'user/create
user=> (def server (create "basedir" "my.war" 8888))
Sep 11, 2014 1:26:14 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8888"]
Sep 11, 2014 1:26:14 AM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFO: Using a shared selector for servlet write/read
Sep 11, 2014 1:26:14 AM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
Sep 11, 2014 1:26:14 AM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/8.0.8
Sep 11, 2014 1:26:15 AM org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
INFO: No global web.xml found
Sep 11, 2014 1:26:15 AM org.apache.jasper.servlet.TldScanner scanJars
INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
Sep 11, 2014 1:26:16 AM org.apache.catalina.util.SessionIdGenerator createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [608] milliseconds.
Sep 11, 2014 1:26:16 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8888"]
#'user/server
jumblerg commented 10 years ago

the missing class appears in an uberjar built with boot 2. it can be required from the boot 2 using the previous build.boot script:

boot.user=> (import 'org.apache.catalina.startup.Tomcat)
org.apache.catalina.startup.Tomcat
boot.user=> (import 'org.apache.tomcat.util.descriptor.web.ServletDef)
org.apache.tomcat.util.descriptor.web.ServletDef
jumblerg commented 10 years ago

also tried commenting out line 95 of boot.clj at micha's suggestion, this had no impact, but did cause the repl to crash: https://github.com/tailrecursion/boot/blob/v2-r1/boot/base/src/main/java/boot/App.java#L95

jumblerg$ boot repl
           clojure.lang.ExceptionInfo:
    data: {:file ".boot/tmp/12568/9dcf3c24/boot/user.clj", :line 40}
java.lang.ExceptionInInitializerError:
      java.lang.IllegalStateException: Var clojure.core/refer is unbound.
                                                   ...
                           clojure.zip/loading--auto--          zip.clj:   12
                                clojure.zip__init.load                 :   12
                            clojure.zip__init.<clinit>
                                                   ...
                           clojure.test__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5528
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
clojure.tools.nrepl.middleware.session/loading--auto--      session.clj:    2
     clojure.tools.nrepl.middleware.session__init.load                 :    2
 clojure.tools.nrepl.middleware.session__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5528
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
            clojure.tools.nrepl.server/loading--auto--       server.clj:    1
                 clojure.tools.nrepl.server__init.load                 :    1
             clojure.tools.nrepl.server__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5524
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
                reply.eval-modes.nrepl/loading--auto--        nrepl.clj:    1
                     reply.eval_modes.nrepl__init.load                 :    1
                 reply.eval_modes.nrepl__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5524
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
                            reply.main/loading--auto--         main.clj:    1
                                 reply.main__init.load                 :    1
                             reply.main__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5524
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
                      boot.repl-client/loading--auto--  repl_client.clj:    1
                           boot.repl_client__init.load                 :    1
                       boot.repl_client__init.<clinit>
                                                   ...
                                  clojure.core/load/fn         core.clj: 5641
                                     clojure.core/load         core.clj: 5640
                                                   ...
                                 clojure.core/load-one         core.clj: 5446
                              clojure.core/load-lib/fn         core.clj: 5486
                                 clojure.core/load-lib         core.clj: 5485
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                clojure.core/load-libs         core.clj: 5524
                                                   ...
                                    clojure.core/apply         core.clj:  626
                                  clojure.core/require         core.clj: 5607
                                                   ...
                                      boot.pod/call-in          pod.clj:  127
                                                   ...
                                      boot.pod/call-in          pod.clj:  132
                                  boot.pod/call-worker          pod.clj:  137
                     boot.task.built-in/fn/fn/fn/fn/fn     built_in.clj:  156
                        boot.task.built-in/fn/fn/fn/fn     built_in.clj:  145
                                   boot.core/run-tasks         core.clj:  292
                                    boot.user/eval1278         user.clj:   47
                                                   ...
                                    boot.main/-main/fn         main.clj:  103
                                       boot.main/-main         main.clj:  103
                                                   ...
                                         boot.App.main         App.java:  185
jumblerg commented 10 years ago

tomcat's sprawling codebase is bloated in classic j2ee style. internal classloader mismanagement may be the issue. consider using winstone instead.

micha commented 10 years ago

Line 95 mentioned above is calling a fn in the shim that uses dynapath to make the AppClassLoader immutable, by the way.

micha commented 10 years ago

Thanks to @tcrawley for this! Apparently Tomcat was using the AppClassLoader instead of the one that it was loaded into. Tomcat needs to be told which classloader to use:

(defn create [dir war port]
  (.mkdirs (io/file dir "webapps"))
  (doto (Tomcat.)
    (as-> % (.setParentClassLoader (.getServer %) (.getContextClassLoader (Thread/currentThread))))
    (.setBaseDir dir)
    (.setPort port)
    (.addWebapp "" war)
    (.start) ))
tobias commented 10 years ago

Yes, otherwise it will fall back to using the AppClassLoader, which is above boot and has no knowledge of the tomcat jars.

jumblerg commented 10 years ago

terrific! thanks for the insight toby. we should probably document this technique in the "writing boot tasks" documentation.

Sent from my phone, please excuse the brevity and typos,

On Sep 11, 2014, at 12:02 PM, Toby Crawley notifications@github.com wrote:

Yes, otherwise it will fall back to using the AppClassLoader, which is above boot and has no knowledge of the tomcat jars.

— Reply to this email directly or view it on GitHub.