Code related to configuring, starting, and stopping the Metabase Jetty web server. | (ns metabase.server.instance (:require [clojure.string :as str] [medley.core :as m] [metabase.config :as config] [metabase.server.protocols :as server.protocols] [metabase.util :as u] [metabase.util.log :as log] [ring.adapter.jetty :as ring-jetty] [ring.util.jakarta.servlet :as servlet]) (:import (jakarta.servlet AsyncContext) (jakarta.servlet.http HttpServletRequest HttpServletResponse) (org.eclipse.jetty.server Request Server) (org.eclipse.jetty.server.handler AbstractHandler StatisticsHandler))) |
(set! *warn-on-reflection* true) | |
(defn- jetty-ssl-config []
(m/filter-vals
some?
{:ssl-port (config/config-int :mb-jetty-ssl-port)
:keystore (config/config-str :mb-jetty-ssl-keystore)
:key-password (config/config-str :mb-jetty-ssl-keystore-password)
:truststore (config/config-str :mb-jetty-ssl-truststore)
:trust-password (config/config-str :mb-jetty-ssl-truststore-password)
:client-auth (when (config/config-bool :mb-jetty-ssl-client-auth)
:need)
:sni-host-check? (when (config/config-bool :mb-jetty-skip-sni)
false)})) | |
(defn- jetty-config []
(cond-> (m/filter-vals
some?
{:port (config/config-int :mb-jetty-port)
:host (config/config-str :mb-jetty-host)
:max-threads (config/config-int :mb-jetty-maxthreads)
:min-threads (config/config-int :mb-jetty-minthreads)
:max-queued (config/config-int :mb-jetty-maxqueued)
:max-idle-time (config/config-int :mb-jetty-maxidletime)})
(config/config-int :mb-jetty-request-header-size) (assoc :request-header-size (config/config-int
:mb-jetty-request-header-size))
(config/config-str :mb-jetty-daemon) (assoc :daemon? (config/config-bool :mb-jetty-daemon))
(config/config-str :mb-jetty-ssl) (-> (assoc :ssl? true)
(merge (jetty-ssl-config))))) | |
(defn- log-config [jetty-config]
(log/info "Launching Embedded Jetty Webserver with config:\n"
(u/pprint-to-str (m/filter-keys
#(not (str/includes? % "password"))
jetty-config)))) | |
(defonce ^:private instance* (atom nil)) | |
THE instance of our Jetty web server, if there currently is one. | (defn instance ^Server [] @instance*) |
(defn- async-proxy-handler ^AbstractHandler [handler timeout]
(proxy [AbstractHandler] []
(handle [_ ^Request base-request ^HttpServletRequest request ^HttpServletResponse response]
(let [^AsyncContext context (doto (.startAsync request)
(.setTimeout timeout))
request-map (servlet/build-request-map request)
raise (fn raise [^Throwable e]
(log/error e "Unexpected exception in endpoint")
(try
(.sendError response 500 (.getMessage e))
(catch Throwable e
(log/error e "Unexpected exception writing error response")))
(.complete context))]
(try
(handler
request-map
(fn [response-map]
(server.protocols/respond (:body response-map) {:request request
:request-map request-map
:async-context context
:response response
:response-map response-map}))
raise)
(catch Throwable e
(log/error e "Unexpected Exception in API request handler")
(raise e))
(finally
(.setHandled base-request true))))))) | |
Create a new async Jetty server with | (defn create-server
^Server [handler options]
;; if any API endpoint functions aren't at the very least returning a channel to fetch the results later after 10
;; minutes we're in serious trouble. (Almost everything 'slow' should be returning a channel before then, but
;; some things like CSV downloads don't currently return channels at this time)
(let [timeout (config/config-int :mb-jetty-async-response-timeout)
handler (async-proxy-handler handler timeout)
stats-handler (doto (StatisticsHandler.)
(.setHandler handler))]
(doto ^Server (#'ring-jetty/create-server (assoc options :async? true))
(.setHandler stats-handler)))) |
Start the embedded Jetty web server. Returns (start-web-server! #'metabase.server.handler/app) | (defn start-web-server!
[handler]
(when-not (instance)
;; NOTE: we always start jetty w/ join=false so we can start the server first then do init in the background
(let [config (jetty-config)
new-server (create-server handler config)]
(log-config config)
;; Only start the server if the newly created server becomes the official new server
;; Don't JOIN yet -- we're doing other init in the background; we can join later
(when (compare-and-set! instance* nil new-server)
(.start new-server)
:started)))) |
Stop the embedded Jetty web server. Returns | (defn stop-web-server!
[]
(let [[^Server old-server] (reset-vals! instance* nil)]
(when old-server
(log/info "Shutting Down Embedded Jetty Webserver")
(.stop old-server)
:stopped))) |