2021-02-15 06:15:16 -05:00
|
|
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
;;
|
2021-04-10 02:43:04 -05:00
|
|
|
;; Copyright (c) UXBOX Labs SL
|
2021-02-15 06:15:16 -05:00
|
|
|
|
2020-06-29 09:07:48 -05:00
|
|
|
(ns app.http
|
|
|
|
(:require
|
2022-03-18 06:34:02 -05:00
|
|
|
["cookies" :as Cookies]
|
|
|
|
["http" :as http]
|
|
|
|
["inflation" :as inflate]
|
|
|
|
["raw-body" :as raw-body]
|
|
|
|
["stream" :as stream]
|
2021-11-16 05:58:59 -05:00
|
|
|
[app.common.logging :as l]
|
2022-03-18 06:34:02 -05:00
|
|
|
[app.common.spec :as us]
|
|
|
|
[app.common.transit :as t]
|
2021-05-05 02:28:12 -05:00
|
|
|
[app.config :as cf]
|
2022-03-18 06:34:02 -05:00
|
|
|
[app.handlers :as handlers]
|
2021-09-21 08:12:46 -05:00
|
|
|
[cuerdas.core :as str]
|
2022-03-18 06:34:02 -05:00
|
|
|
[lambdaisland.uri :as u]
|
|
|
|
[promesa.core :as p]))
|
2020-06-29 09:07:48 -05:00
|
|
|
|
2021-11-16 05:58:59 -05:00
|
|
|
(l/set-level! :info)
|
|
|
|
|
2022-03-18 06:34:02 -05:00
|
|
|
(defprotocol IStreamableResponseBody
|
|
|
|
(write-body! [_ response]))
|
2020-06-29 09:07:48 -05:00
|
|
|
|
2022-03-18 06:34:02 -05:00
|
|
|
(extend-protocol IStreamableResponseBody
|
|
|
|
string
|
|
|
|
(write-body! [data response]
|
|
|
|
(.write ^js response data)
|
|
|
|
(.end ^js response))
|
|
|
|
|
|
|
|
js/Buffer
|
|
|
|
(write-body! [data response]
|
|
|
|
(.write ^js response data)
|
|
|
|
(.end ^js response))
|
|
|
|
|
|
|
|
stream/Stream
|
|
|
|
(write-body! [data response]
|
|
|
|
(.pipe ^js data response)
|
|
|
|
(.on ^js data "error" (fn [cause]
|
|
|
|
(js/console.error cause)
|
|
|
|
(.end response)))))
|
|
|
|
|
|
|
|
(defn- handle-response
|
|
|
|
[{:keys [:response/body
|
|
|
|
:response/headers
|
|
|
|
:response/status
|
|
|
|
response]
|
|
|
|
:as exchange}]
|
|
|
|
(let [status (or status 200)
|
|
|
|
headers (clj->js headers)
|
|
|
|
body (or body "")]
|
|
|
|
(.writeHead ^js response status headers)
|
|
|
|
(write-body! body response)))
|
|
|
|
|
|
|
|
(defn- parse-headers
|
|
|
|
[req]
|
|
|
|
(let [orig (unchecked-get req "headers")]
|
|
|
|
(persistent!
|
|
|
|
(reduce #(assoc! %1 (str/lower %2) (unchecked-get orig %2))
|
|
|
|
(transient {})
|
|
|
|
(js/Object.keys orig)))))
|
|
|
|
|
|
|
|
(defn- wrap-body-params
|
|
|
|
[handler]
|
|
|
|
(let [opts #js {:limit "2mb" :encoding "utf8"}]
|
|
|
|
(fn [{:keys [:request/method :request/headers request] :as exchange}]
|
|
|
|
(let [ctype (get headers "content-type")]
|
|
|
|
(if (= method "post")
|
|
|
|
(-> (raw-body (inflate request) opts)
|
|
|
|
(p/then (fn [data]
|
|
|
|
(cond-> data
|
|
|
|
(= ctype "application/transit+json")
|
|
|
|
(t/decode-str))))
|
|
|
|
(p/then (fn [data]
|
|
|
|
(handler (assoc exchange :request/body-params data)))))
|
|
|
|
(handler exchange))))))
|
|
|
|
|
|
|
|
(defn- wrap-params
|
|
|
|
[handler]
|
|
|
|
(fn [{:keys [:request/body-params :request/query-params] :as exchange}]
|
|
|
|
(handler (assoc exchange :request/params (merge query-params body-params)))))
|
|
|
|
|
|
|
|
(defn- wrap-response-format
|
|
|
|
[handler]
|
|
|
|
(fn [exchange]
|
|
|
|
(p/then
|
|
|
|
(handler exchange)
|
|
|
|
(fn [{:keys [:response/body :response/status] :as exchange}]
|
|
|
|
(cond
|
|
|
|
(map? body)
|
|
|
|
(let [data (t/encode-str body {:type :json-verbose})]
|
|
|
|
(-> exchange
|
|
|
|
(assoc :response/body data)
|
|
|
|
(assoc :response/status 200)
|
|
|
|
(update :response/headers assoc "content-type" "application/transit+json")
|
|
|
|
(update :response/headers assoc "content-length" (count data))))
|
2021-05-05 02:28:12 -05:00
|
|
|
|
2022-03-18 06:34:02 -05:00
|
|
|
(and (nil? body)
|
|
|
|
(= 200 status))
|
|
|
|
(-> exchange
|
|
|
|
(assoc :response/body "")
|
|
|
|
(assoc :response/status 204)
|
|
|
|
(assoc :response/headers {"content-length" 0}))
|
|
|
|
|
|
|
|
:else
|
|
|
|
exchange)))))
|
|
|
|
|
|
|
|
(defn- wrap-query-params
|
|
|
|
[handler]
|
|
|
|
(fn [{:keys [:request/uri] :as exchange}]
|
|
|
|
(handler (assoc exchange :request/query-params (u/query-string->map (:query uri))))))
|
|
|
|
|
|
|
|
(defn- wrap-error
|
|
|
|
[handler on-error]
|
|
|
|
(fn [exchange]
|
|
|
|
(-> (p/do (handler exchange))
|
|
|
|
(p/catch (fn [cause] (on-error cause exchange))))))
|
|
|
|
|
|
|
|
(defn- wrap-auth
|
|
|
|
[handler cookie-name]
|
|
|
|
(fn [{:keys [:request/cookies] :as exchange}]
|
|
|
|
(let [token (.get ^js cookies cookie-name)]
|
|
|
|
(handler (cond-> exchange token (assoc :request/auth-token token))))))
|
|
|
|
|
|
|
|
(defn- create-adapter
|
|
|
|
[handler]
|
|
|
|
(fn [req res]
|
|
|
|
(let [cookies (Cookies. req res)
|
|
|
|
headers (parse-headers req)
|
|
|
|
uri (u/uri (unchecked-get req "url"))
|
|
|
|
exchange {:request/method (str/lower (unchecked-get req "method"))
|
|
|
|
:request/path (:path uri)
|
|
|
|
:request/uri uri
|
|
|
|
:request/headers headers
|
|
|
|
:request/cookies cookies
|
|
|
|
:request req
|
|
|
|
:response res}]
|
|
|
|
(-> (p/do (handler exchange))
|
|
|
|
(p/then handle-response)))))
|
|
|
|
|
|
|
|
(defn- create-server
|
|
|
|
[handler]
|
|
|
|
(.createServer ^js http (create-adapter handler)))
|
|
|
|
|
|
|
|
(def instance (atom nil))
|
2021-09-21 08:12:46 -05:00
|
|
|
|
2021-05-05 02:28:12 -05:00
|
|
|
(defn init
|
|
|
|
[]
|
2022-03-18 06:34:02 -05:00
|
|
|
(let [handler (-> handlers/handler
|
|
|
|
(wrap-auth "auth-token")
|
|
|
|
(wrap-response-format)
|
|
|
|
(wrap-params)
|
|
|
|
(wrap-query-params)
|
|
|
|
(wrap-body-params)
|
|
|
|
(wrap-error handlers/on-error))
|
|
|
|
server (create-server handler)
|
2021-05-05 02:28:12 -05:00
|
|
|
port (cf/get :http-server-port 6061)]
|
|
|
|
(.listen server port)
|
2021-11-16 05:58:59 -05:00
|
|
|
(l/info :msg "welcome to penpot"
|
2022-03-18 06:34:02 -05:00
|
|
|
:module "exporter"
|
|
|
|
:version (:full @cf/version))
|
2021-11-16 05:58:59 -05:00
|
|
|
(l/info :msg "starting http server" :port port)
|
2021-05-05 02:28:12 -05:00
|
|
|
(reset! instance server)))
|
2020-06-29 09:07:48 -05:00
|
|
|
|
2021-05-05 02:28:12 -05:00
|
|
|
(defn stop
|
|
|
|
[]
|
|
|
|
(if-let [server @instance]
|
|
|
|
(p/create (fn [resolve]
|
|
|
|
(.close server (fn []
|
2021-11-16 05:58:59 -05:00
|
|
|
(l/info :msg "shutdown http server")
|
2021-05-05 02:28:12 -05:00
|
|
|
(resolve)))))
|
|
|
|
(p/resolved nil)))
|