Skip to content

Commit

Permalink
[#6] Updated docs and redirect function (@alekcz)
Browse files Browse the repository at this point in the history
Ready for v0.0.2
  • Loading branch information
alekcz authored Jul 22, 2021
2 parents 52e7dcd + 973c4a9 commit a572df9
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 33 deletions.
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
## Introduction

Too long have we hustled to deploy Clojure websites. Too long have we spun up one server instance per site. Too long have we reminisced about PHP. Today we enjoy the benefits of both. Welcome to PCP.
Too long have we hustled to deploy Clojure websites. Too long have we spun up one server instance per site. Too long have we reminisced about the simplicty of deploying PHP. Today we enjoy the benefits of both. Welcome to PCP.

### Status
Active development. Stabilizing.
Expand All @@ -21,7 +21,7 @@ Latest version: `v0.0.1`

### Non-goals

* Performance. _PCP should be sufficient for prototypes and small websites (< 400 req/s)_
* Performance. _PCP should be sufficient for prototypes and small websites (< 1400 req/s)_

### Demo site

Expand Down Expand Up @@ -240,8 +240,12 @@ Removes a key from the cache
The request map. The request map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC).

#### pcp/response
`(pcp/response [status body mime-type])`
A convenience function for generating a response map. Responses are simply Clojure maps that conform to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC) and can be written by hand too.
`(pcp/response status body mime-type)`
A convenience function for generating a response map. Response map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC).

#### pcp/redirect
`(pcp/redirect target)`
A convenience function for generating a redirect. Response map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC).

#### pcp/render-html
`(pcp/render-html options & content)`
Expand Down Expand Up @@ -306,16 +310,16 @@ In addition to the core clojure namespaces in [sci](https://github.com/borkdude/

## Performance
A modest PCP website can handle its own pretty well.
This test was run for 80 minutes with 400 VUs and 400 req/s. Of the 1.9M requests generated, only 26 failed.
This test was run for 90 minutes with 500 VUs and 1400 req/s. Of the 7.7M requests generated and 0 failed.

<img src="assets/performance/k6.png" width="800px">
<img src="assets/performance/k6-0.0.2.png" width="800px">

You can see the spike in CPU and bandwidth on the droplet, but the CPU never saturates.
You can see the spike in CPU and bandwidth on the $5 droplet, but the CPU never saturates.

<img src="assets/performance/do.png" width="800px">
<img src="assets/performance/do-0.0.2.png" width="800px">

These are by no means comprehensive benchmarks but give you a sense of what a PCP server can withstand for a simple use case.
Going above 400 req/s generally results in bad things happening. You can test your own site using [k6](https://k6.io/) with the instructions in [loadtest.js](./loadtest.js)
Going above 1400 req/s generally results in bad things happening. You can test your own site using [k6](https://k6.io/) with the instructions in [loadtest.js](./loadtest.js)

# Roadmap & releases

Expand All @@ -325,7 +329,7 @@ Going above 400 req/s generally results in bad things happening. You can test yo
- [x] Invoke scripts from Nginx
- [x] Emulate Nginx environment locally
- [x] Store secrets in projects
- [x] Restrict `slurp` and `spit` to project root
- [x] Restrict `pcp/slurp` and `pcp/spit` to project root
- [x] Cache expensive operations
- [x] Expose request data to scripts
- [x] Require external namespaces as in Clojure
Expand All @@ -334,13 +338,16 @@ Going above 400 req/s generally results in bad things happening. You can test yo
- [x] Load test a production deployment

## Release 0.0.2
- [ ] Perfomance improvements
- [x] Perfomance improvements.
- [x] Only read source file from disk if it is not in cache (LRU) or has been modified
- [x] Add `pcp/slurp-upload` for reading temporary files
- [x] Add `pcp/redirect` for convenience redirect

## Release 0.1.0
- [ ] Store passphrases with [konserve](https://github.com/replikativ/konserve)
- [ ] Add sponsorship button
- [ ] Deploy without logging in to VPS
- [ ] Switch to [malli-cli](https://github.com/piotr-yuxuan/malli-cli)
- [ ] Deploy without ssh-ing into VPS
- [ ] More robust CLI processing
- [ ] Automate deployment from repo

## Release 0.2.0
Expand Down
Binary file added assets/performance/do-0.0.2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/performance/k6-0.0.2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion loadtest.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export let options = {
],
};
export default function() {
let response = http.get("http://localhost:3000");
let response = http.get("http://localhost:3000/hello.clj");
};
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject pcp "0.0.2-beta.7"
(defproject pcp "0.0.2"
:description "PCP: Clojure Processor - A Clojure replacement for PHP"
:url "https://github.com/alekcz/pcp"
:license {:name "The MIT License"
Expand Down
2 changes: 1 addition & 1 deletion resources/PCP_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.0.2-beta.7
v0.0.2
2 changes: 2 additions & 0 deletions resources/pcp-templates/hello.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(require '[pcp :as pcp])
(pcp/response 200 "pew pew" "text/plain")
30 changes: 21 additions & 9 deletions src/pcp/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
[clojure.core.cache.wrapped :as c])
(:import [java.net URLDecoder]
[java.io File]
[java.lang Exception]
[org.apache.commons.codec.digest DigestUtils])
(:gen-class))

Expand All @@ -41,6 +42,11 @@
(binding [util/*escape-strings?* false]
(apply compiler/render-html contents)))

(defn redirect [target]
(-> (resp/response "")
(resp/status 302)
(resp/header "Location" target)))

(defn format-response [status body mime-type]
(-> (resp/response body)
(resp/status status)
Expand All @@ -57,12 +63,12 @@
(keyword (str (h/uuid any))))

(defn read-source [path]
{:modified (.lastModified (io/file path))
{:modified (.lastModified ^File (io/file path))
:contents (slurp path)})

(defn get-source [path]
(let [sk (->keyword path)
last-modified (.lastModified (io/file path))
last-modified (.lastModified ^File (io/file path))
cached-source (c/lookup source-cache sk)
s (when (or (nil? cached-source)
(> last-modified (:modified cached-source)))
Expand Down Expand Up @@ -109,6 +115,11 @@
target-path (-> target ^File io/as-file (.getCanonicalPath))]
(str/starts-with? target-path parent-path))))

(defn validate-response [resp uri]
(if-not (resp/response? resp)
(throw (ex-info (str "Invalid response in: " uri) {:response resp}))
resp))

(defn temporary? [^File target]
(when target
(let [target-path (.getAbsolutePath target)
Expand All @@ -127,9 +138,9 @@
(defn run-script [url-path &{:keys [root request status]}]
(let [status (atom status)
path' (URLDecoder/decode url-path "UTF-8")
path (if (-> path' (io/file) (.exists))
path (if (-> path' ^File (io/file) (.exists))
path'
(when (-> (str root "/404.clj") (io/file) (.exists))
(when (-> (str root "/404.clj") ^File (io/file) (.exists))
(reset! status 404)
(str root "/404.clj")))]
(if (nil? path)
Expand All @@ -140,14 +151,15 @@
parent (longer root (-> ^File file (.getParentFile) (.getCanonicalPath)))
response (atom nil)
keygen (fn [path k] (keyword (str (h/uuid [path k]))))]
(if (string? source)
(when (string? source)
(let [opts (-> { :load-fn #(include root %)
:namespaces (merge includes { '$ {'persist! (fn [k v] (k (c/through-cache cache (keygen root k) (constantly v))))
'retrieve (fn [k] (c/lookup cache (keygen root k)))}
'pcp {'persist persist
'clear (fn [k] (c/evict cache (keygen root k)))
'request request
'response (fn [status body mime-type] (reset! response (format-response status body mime-type)))
'redirect (fn [target] (reset! response (redirect target)))
'html render-html
'render-html render-html
'html-unescaped render-html-unescaped
Expand All @@ -163,14 +175,14 @@
_ (parser/set-resource-path! root)
result (process-script source opts)
_ (selmer.parser/set-resource-path! nil)
final-result (if (nil? @response) result @response)]
(if @status (assoc final-result :status @status) final-result))
(format-response 404 "" nil))))))
final-result (if (nil? @response) result @response)
response (if @status (assoc final-result :status @status) final-result) ]
(validate-response response path)))))))

(defn e500 [root req message]
(let [uri "/500.clj"
error-page (str root uri)]
(if (-> error-page (io/file) (.exists))
(if (-> error-page ^File (io/file) (.exists))
(try
(run-script error-page :root root :request (assoc req :error message :uri uri) :status 500)
(catch Exception e (format-response 500 (.getMessage e) nil)))
Expand Down
11 changes: 7 additions & 4 deletions src/pcp/utility.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

(def root (atom nil))
(def scgi (atom "9000"))
(def version "v0.0.2-beta.7")
(def version "v0.0.2")

(defn keydb []
(or (env :pcp-keydb) "/usr/local/etc/pcp-db"))
Expand Down Expand Up @@ -67,7 +67,7 @@ Options:
(resp/content-type mime))))

(defn file-exists? [path]
(-> path ^File io/file .exists))
(-> path ^File (io/file) .exists))

(defn serve-file [path]
(file-response path (io/file path)))
Expand All @@ -90,8 +90,8 @@ Options:
:else (forward full (:scgi-port opts))))))

(defn run-file [path scgi-port]
(let [path' (-> path io/file (.getCanonicalPath))
root (-> path' io/file (.getParentFile) (.getCanonicalPath))
(let [path' (-> path ^File (io/file) (.getCanonicalPath))
root (-> path' ^File (io/file) ^File (.getParentFile) (.getCanonicalPath))
final-path (-> path' (str/replace root "/") (str/replace "//" "/"))
request {:headers {"document-root" root} :uri final-path :request-method :get :body ""}
resp (forward request scgi-port)]
Expand Down Expand Up @@ -208,6 +208,9 @@ Options:
(spit
(str path "/public/index.clj")
(slurp (str (template-path) "/index.clj")))
(spit
(str path "/public/hello.clj")
(slurp (str (template-path) "/hello.clj")))
(spit
(str path "/README.md")
(slurp (str (template-path) "/README.md")))
Expand Down
8 changes: 6 additions & 2 deletions test-resources/process-includes.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
(require '[included :as i])
(require '[pcp :as pcp])

{:ans (+ 5578 i/tester)
:working (i/working?)}
(pcp/response
200
{:ans (+ 5578 i/tester)
:working (i/working?)}
"text/plain")
3 changes: 2 additions & 1 deletion test/pcp/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[clojure.java.io :as io]
[pcp.resp :as resp]
[pcp.core :as core]
[cheshire.core :as json]
[clojure.string :as str])
(:import [java.io File]))

Expand Down Expand Up @@ -58,7 +59,7 @@
uri "/process-includes.clj"
scgi-request {:headers {"document-root" root} :uri uri}
_ (core/handler scgi-request)
ans (core/run-script (str root uri))]
ans (:body (core/run-script (str root uri)))]
(is (= (:ans ans) 5678))
(is (true? (:working ans))))))

Expand Down
11 changes: 10 additions & 1 deletion test/pcp/utility_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@
_ (try (delete-recursively project) (catch Exception _ nil))
_ (io/make-parents (str "./test-resources/pcp-db/" project ".db"))
_ (new-folder root)
_ (clojure.java.io/delete-file (str root "/404.clj"))
_ (spit (str root "/error.clj") "(require '[asdad.sad :as fake])")
_ (spit (str root "/500.clj") "(pcp/response :break-me :break-it :lool)")
_ (utility/stop-scgi)
scgi (core/-main)
_ (Thread/sleep boot-time)
Expand All @@ -183,6 +186,7 @@
(is (= file-eval-expected file-eval))
(is (= file-eval-expected file-eval2))
(is (thrown? Exception (client/get (str "http://localhost:3000/not-there"))))
(is (thrown? Exception (client/get (str "http://localhost:3000/error.clj"))))
(local)
(Thread/sleep (* 5 boot-time))
(let [local2 (utility/-main "--serve" "./")
Expand Down Expand Up @@ -214,6 +218,8 @@
_ (spit (str root "/temp.clj") (str "(pcp/spit \"" tempfile "\" \"123456\")"
"(pcp/response 200 (pcp/slurp \"" tempfile "\") \"text/plain\")"))
_ (spit (str root "/404.clj") "(pcp/response 200 \"404page\" \"text/plain\")")
_ (spit (str root "/redirect.clj") "(pcp/redirect \"/hello.clj\")")
_ (spit (str root "/hello.clj") "(pcp/response 200 \"pew pew\" \"text/plain\")")
_ (spit (str root "/error.clj") "(require '[asdad.sad :as fake])")
_ (spit (str root "/500.clj") "(pcp/response 200 \"500page\" \"text/plain\")")
_ (Thread/sleep boot-time)
Expand All @@ -222,13 +228,15 @@
:multipart [{:name "sangoku" :content (FileInputStream. (clojure.java.io/file (str root "/random.txt" ))) :filename "random.txt"}]}))
res2 @(http/get (str "http://localhost:" port "/does-not-exist"))
res3 @(http/get (str "http://localhost:" port "/error.clj"))
res4 @(http/get (str "http://localhost:" port "/temp.clj"))]
res4 @(http/get (str "http://localhost:" port "/temp.clj"))
res5 @(http/get (str "http://localhost:" port "/redirect.clj"))]
(is (= randy res))
(is (= 404 (:status res2)))
(is (= "404page" (:body res2)))
(is (= 500 (:status res3)))
(is (= "500page" (:body res3)))
(is (= "123456" (:body res4)))
(is (= "pew pew" (:body res5)))
(local)
(scgi))))

Expand All @@ -241,5 +249,6 @@
(let [_ (utility/new-project "tmp")]
(is (= (slurp "tmp/public/api/info.clj") (slurp "resources/pcp-templates/api/info.clj")))
(is (= (slurp "tmp/README.md") (slurp "resources/pcp-templates/README.md")))
(is (= (slurp "tmp/public/hello.clj") (slurp "resources/pcp-templates/hello.clj")))
(is (= (slurp "tmp/public/index.clj") (slurp "resources/pcp-templates/index.clj"))))))

0 comments on commit a572df9

Please sign in to comment.