0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

Stream transit encoding to the response output-stream.

Instead of in-memmory encoding. This will prevent many OOM errors.
This commit is contained in:
Andrey Antukh 2022-01-17 23:39:26 +01:00 committed by Alonso Torres
parent 9cf5258053
commit 7afb3e2c6d
2 changed files with 42 additions and 24 deletions

View file

@ -141,11 +141,11 @@
:get ws}]
["/api" {:middleware [[middleware/cors]
[middleware/etag]
[middleware/params]
[middleware/multipart-params]
[middleware/keyword-params]
[middleware/format-response-body]
[middleware/etag]
[middleware/parse-request-body]
[middleware/errors errors/handle]
[middleware/cookies]]}

View file

@ -12,10 +12,12 @@
[app.util.json :as json]
[buddy.core.codecs :as bc]
[buddy.core.hash :as bh]
[ring.core.protocols :as rp]
[ring.middleware.cookies :refer [wrap-cookies]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
[ring.middleware.params :refer [wrap-params]]))
[ring.middleware.params :refer [wrap-params]]
[yetti.adapter :as yt]))
(defn wrap-server-timing
[handler]
@ -65,11 +67,33 @@
{:name ::parse-request-body
:compile (constantly wrap-parse-request-body)})
(defn buffered-output-stream
"Returns a buffered output stream that ignores flush calls. This is
needed because transit-java calls flush very aggresivelly on each
object write."
[^java.io.OutputStream os ^long chunk-size]
(proxy [java.io.BufferedOutputStream] [os (int chunk-size)]
;; Explicitly do not forward flush
(flush [])
(close []
(proxy-super flush)
(proxy-super close))))
(def ^:const buffer-size (:http/output-buffer-size yt/base-defaults))
(defn- transit-streamable-body
[data opts]
(reify rp/StreamableResponseBody
(write-body-to-stream [_ _ output-stream]
;; Use the same buffer as jetty output buffer size
(with-open [bos (buffered-output-stream output-stream buffer-size)]
(let [tw (t/writer bos opts)]
(t/write! tw data))))))
(defn- impl-format-response-body
[response request]
[response {:keys [query-params] :as request}]
(let [body (:body response)
params (:query-params request)
opts {:type (if (contains? params "transit_verbose") :json-verbose :json)}]
opts {:type (if (contains? query-params "transit_verbose") :json-verbose :json)}]
(cond
(:ws response)
@ -78,7 +102,7 @@
(coll? body)
(-> response
(update :headers assoc "content-type" "application/transit+json")
(assoc :body (t/encode body opts)))
(assoc :body (transit-streamable-body body opts)))
(nil? body)
(assoc response :status 204 :body "")
@ -131,24 +155,18 @@
(defn wrap-etag
[handler]
(letfn [(generate-etag [{:keys [body] :as response}]
(str "W/\"" (-> body bh/blake2b-128 bc/bytes->hex) "\""))
(get-match [{:keys [headers] :as request}]
(get headers "if-none-match"))]
(fn [request]
(let [response (handler request)]
(if (= :get (:request-method request))
(let [etag (generate-etag response)
match (get-match request)
response (update response :headers #(assoc % "ETag" etag))]
(cond-> response
(and (string? match)
(= :get (:request-method request))
(= etag match))
(-> response
(assoc :body "")
(assoc :status 304))))
response)))))
(letfn [(encode [data]
(when (string? data)
(str "W/\"" (-> data bh/blake2b-128 bc/bytes->hex) "\"")))]
(fn [{method :request-method headers :headers :as request}]
(cond-> (handler request)
(= :get method)
(as-> $ (if-let [etag (-> $ :body meta :etag encode)]
(cond-> (update $ :headers assoc "etag" etag)
(= etag (get headers "if-none-match"))
(-> (assoc :body "")
(assoc :status 304)))
$))))))
(def etag
{:name ::etag