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