🎉 Add stacked exports.

This commit is contained in:
Andrey Antukh 2020-07-02 14:48:17 +02:00 committed by Hirunatan
parent a8d5cdc29f
commit 2fb4e72240
14 changed files with 549 additions and 208 deletions

View file

@ -15,11 +15,13 @@
(f page)))))
(defn emulate!
[page {:keys [viewport user-agent]
:or {user-agent USER-AGENT}}]
[page {:keys [viewport user-agent scale]
:or {user-agent USER-AGENT
scale 1}}]
(let [[width height] viewport]
(.emulate page #js {:viewport #js {:width width
:height height}
:height height
:deviceScaleFactor scale}
:userAgent user-agent})))
(defn navigate!
@ -33,10 +35,20 @@
(.waitFor ^js page ms))
(defn screenshot
([page] (screenshot page nil))
([page {:keys [full-page?]
:or {full-page? true}}]
(.screenshot ^js page #js {:fullPage full-page? :omitBackground true})))
([frame] (screenshot frame nil))
([frame {:keys [full-page? omit-background?]
:or {full-page? false
omit-background? false}}]
(.screenshot ^js frame #js {:fullPage full-page?
:omitBackground omit-background?})))
(defn eval!
[frame f]
(.evaluate ^js frame f))
(defn select
[frame selector]
(.$ ^js frame selector))
(defn set-cookie!
[page {:keys [key value domain]}]
@ -47,16 +59,15 @@
(defn start!
([] (start! nil))
([{:keys [concurrency concurrency-strategy]
:or {concurrency 2
concurrency-strategy :browser}}]
:or {concurrency 10
concurrency-strategy :incognito}}]
(let [ccst (case concurrency-strategy
:browser (.-CONCURRENCY_BROWSER ^js ppc/Cluster)
:incognito (.-CONCURRENCY_CONTEXT ^js ppc/Cluster)
:page (.-CONCURRENCY_PAGE ^js ppc/Cluster))
opts #js {:concurrency ccst
:maxConcurrency concurrency
:puppeteerOptions #js {:args #js ["--no-sandbox"
"--explicitly-allowed-ports=6000"]}}]
:puppeteerOptions #js {:args #js ["--no-sandbox"]}}]
(.launch ^js ppc/Cluster opts))))
(defn stop!

View file

@ -3,13 +3,14 @@
[promesa.core :as p]
[lambdaisland.glogi :as log]
[app.browser :as bwr]
[app.http.screenshot :refer [bitmap-handler
page-handler]]
[app.http.bitmap-export :refer [bitmap-export-handler]]
[app.util.transit :as t]
[reitit.core :as r]
[cuerdas.core :as str]
["koa" :as koa]
["http" :as http])
["http" :as http]
["inflation" :as inflate]
["raw-body" :as raw-body])
(:import
goog.Uri))
@ -26,11 +27,7 @@
[router ctx]
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))]
(when-let [match (r/match-by-path router (.getPath uri))]
(let [qparams (query-params uri)
params {:path (:path-params match) :query qparams}]
(assoc match
:params params
:query-params qparams)))))
(assoc match :query-params (query-params uri)))))
(defn- handle-error
[error request]
@ -72,15 +69,33 @@
(transient {})
(js/Object.keys orig)))))
(def parse-body?
#{"POST" "PUT" "DELETE"})
(defn- parse-body
[ctx]
(let [headers (unchecked-get ctx "headers")
ctype (unchecked-get headers "content-type")]
(when (parse-body? (.-method ^js ctx))
(-> (inflate (.-req ^js ctx))
(raw-body #js {:limit "5mb" :encoding "utf8"})
(p/then (fn [data]
(cond-> data
(= ctype "application/transit+json")
(t/decode))))))))
(defn- wrap-handler
[f extra]
(fn [ctx]
(let [cookies (unchecked-get ctx "cookies")
headers (parse-headers ctx)
request (assoc extra
:ctx ctx
:headers headers
:cookies cookies)]
(p/let [cookies (unchecked-get ctx "cookies")
headers (parse-headers ctx)
body (parse-body ctx)
request (assoc extra
:method (str/lower (unchecked-get ctx "method"))
:body body
:ctx ctx
:headers headers
:cookies cookies)]
(-> (p/do! (f request))
(p/then (fn [rsp]
(when (map? rsp)
@ -91,16 +106,20 @@
(def routes
[["/export"
["/bitmap" {:handler bitmap-handler}]
["/page" {:handler page-handler}]]])
["/bitmap" {:handler bitmap-export-handler}]]])
(defn- router-handler
[router]
(fn [{:keys [ctx] :as req}]
(fn [{:keys [ctx body] :as request}]
(let [route (match router ctx)
request (assoc req
params (merge {}
(:query-params route)
(:path-params route)
(when (map? body) body))
request (assoc request
:route route
:params (:params route))
:params params)
handler (get-in route [:data :handler])]
(if (and route handler)
(handler request)

View file

@ -0,0 +1,120 @@
(ns app.http.bitmap-export
(:require
[cuerdas.core :as str]
[app.browser :as bwr]
[app.config :as cfg]
[app.zipfile :as zip]
[lambdaisland.glogi :as log]
[cljs.spec.alpha :as s]
[promesa.core :as p]
[uxbox.common.exceptions :as exc :include-macros true]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.common.spec :as us])
(:import
goog.Uri))
(defn- screenshot-object
[browser {:keys [page-id object-id token scale suffix]}]
(letfn [(handle [page]
(let [path (str "/render-object/" page-id "/" object-id)
uri (doto (Uri. (:public-uri cfg/config))
(.setPath "/")
(.setFragment path))
cookie {:domain (str (.getDomain uri)
":"
(.getPort uri))
:key "auth-token"
:value token}]
(log/info :uri (.toString uri))
(screenshot page (.toString uri) cookie)))
(screenshot [page uri cookie]
(p/do!
(bwr/emulate! page {:viewport [1920 1080]
:scale scale})
(bwr/set-cookie! page cookie)
(bwr/navigate! page uri)
(bwr/eval! page (js* "() => document.body.style.background = 'transparent'"))
(p/let [dom (bwr/select page "#screenshot")]
(bwr/screenshot dom {:omit-background? true
:type type}))))]
(bwr/exec! browser handle)))
(s/def ::name ::us/string)
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::suffix ::us/string)
(s/def ::type ::us/keyword)
(s/def ::suffix string?)
(s/def ::scale number?)
(s/def ::export
(s/keys :req-un [::type ::suffix ::scale]))
(s/def ::exports (s/coll-of ::export :kind vector?))
(s/def ::bitmap-handler-params
(s/keys :req-un [::page-id ::object-id ::name ::exports]))
(declare handle-single-export)
(declare handle-multiple-export)
(defn bitmap-export-handler
[{:keys [params browser cookies] :as request}]
(let [{:keys [exports page-id object-id name]} (us/conform ::bitmap-handler-params params)
token (.get ^js cookies "auth-token")]
(case (count exports)
0 (exc/raise :type :validation :code :missing-exports)
1 (handle-single-export
request
(assoc (first exports)
:name name
:token token
:page-id page-id
:object-id object-id))
(handle-multiple-export
request
(->> (d/enumerate exports)
(map (fn [[index item]]
(assoc item
:name name
:index index
:token token
:page-id page-id
:object-id object-id))))))))
(defn perform-bitmap-export
[browser params]
(p/let [content (screenshot-object browser params)]
{:content content
:filename (str (str/slug (:name params))
(if (not (str/blank? (:suffix params "")))
(:suffix params "")
(let [index (:index params 0)]
(when (pos? index)
(str "-" (inc index)))))
".png")
:length (alength content)
:mime-type "image/png"}))
(defn handle-single-export
[{:keys [browser]} params]
(p/let [result (perform-bitmap-export browser params)]
{:status 200
:body (:content result)
:headers {"content-type" (:mime-type result)
"content-length" (:length result)}}))
(defn handle-multiple-export
[{:keys [browser]} exports]
(let [proms (map (partial perform-bitmap-export browser) exports)]
(-> (p/all proms)
(p/then (fn [results]
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
(p/then (fn [fzip]
{:status 200
:headers {"content-type" "application/zip"}
:body (.generateNodeStream ^js fzip)})))))

View file

@ -1,73 +0,0 @@
(ns app.http.screenshot
(:require
[app.browser :as bwr]
[app.config :as cfg]
[lambdaisland.glogi :as log]
[cljs.spec.alpha :as s]
[promesa.core :as p]
[uxbox.common.exceptions :as exc :include-macros true]
[uxbox.common.spec :as us])
(:import
goog.Uri))
(defn- load-and-screenshot
[page url cookie]
(p/do!
(bwr/emulate! page {:viewport [1920 1080]})
(bwr/set-cookie! page cookie)
(bwr/navigate! page url)
(bwr/sleep page 500)
(.evaluate page (js* "() => document.body.style.background = 'transparent'"))
;; (.screenshot ^js page #js {:omitBackground true :fullPage true})
(p/let [dom (.$ page "#screenshot")]
(.screenshot ^js dom #js {:omitBackground true}))))
(defn- take-screenshot
[browser {:keys [page-id object-id token]}]
(letfn [(on-browser [page]
(let [path (str "/render-object/" page-id "/" object-id)
uri (doto (Uri. (:public-uri cfg/config))
(.setPath "/")
(.setFragment path))
cookie {:domain (str (.getDomain uri)
":"
(.getPort uri))
:key "auth-token"
:value token}]
(log/info :uri (.toString uri))
(load-and-screenshot page (.toString uri) cookie)))]
(bwr/exec! browser on-browser)))
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::bitmap-handler-params
(s/keys :req-un [::page-id ::object-id]))
(defn bitmap-handler
[{:keys [params browser cookies] :as request}]
(let [params (us/conform ::bitmap-handler-params (:query params))
token (.get ^js cookies "auth-token")]
(-> (take-screenshot browser {:page-id (:page-id params)
:object-id (:object-id params)
:token token})
(p/then (fn [result]
{:status 200
:body result
:headers {"content-type" "image/png"
"content-length" (alength result)}})))))
(defn page-handler
[{:keys [params browser] :as request}]
(letfn [(screenshot [page uri]
(p/do!
(bwr/emulate! page {:viewport [1920 1080]})
(bwr/navigate! page uri)
(bwr/sleep page 500)
;; (.evaluate page (js* "() => document.body.style.background = 'transparent'"))
(.screenshot ^js page #js {:omitBackground false})))]
(p/let [uri (get-in params [:query :uri])
sht (bwr/exec! browser #(screenshot % uri))]
{:status 200
:body sht
:headers {"content-type" "image/png"
"content-length" (alength sht)}})))

View file

@ -0,0 +1,13 @@
(ns app.zipfile
(:require
["jszip" :as jszip]))
(defn create
[]
(new jszip))
(defn add!
[zfile name data]
(.file ^js zfile name data)
zfile)