A Nextjournal Webserver Environment With Ngrok and Clojure

Introduced in 1969, the PDP-15 was DEC's fifth (and last) 18-bit computer system. [Image: Digital Equipment Corporation via Bob Supnik]

Server

Localhost Tunnel: ngrok

Install ngrok.

curl -sS https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip -o ngrok.zip
unzip /ngrok.zip
4.5s
ngrok-client-server-env (Bash in Clojure)

-bind-tls=true

Settings for the ngrok configuration file. TODO: Why not Port 5000?

region: us
console_ui: true
tunnels:
  btf:
    proto: http
    addr: 5000
    subdomain: btf
ngrok.yml
YAML

Grab your authtoken from ngrok. The token will authenticate this notebook with the ngrok service.

Store the token in your Nextjournal secret vault and add it to the environment.

Prepend the authtoken to the configuration file above, ngrok.yml.

echo 'authtoken:' $ngrok | cat - ngrok.yml > /tmp/out && mv /tmp/out ngrok.yml
1.2s
ngrok-client-server-env (Bash in Clojure)

Run ngrok in the background using nohup. Unfortunately, there are no plans to offer a daemon service in the basic ngrok package.

nohup /ngrok start --all --config="/ngrok.yml" &> /tmp/ngrok.log & sleep 1
tail /tmp/ngrok.log
2.6s
ngrok-client-server-env (Bash in Clojure)

The service is up and running. To stop the ngrok tunnel, use the kill command with the process ID: kill <PID> or kill -9 "$(pgrep ngrok)".

ps aux
1.7s
ngrok-client-server-env (Bash in Clojure)
# kill 982
# kill -9 "$(pgrep ngrok)"
1.3s
ngrok-client-server-env (Bash in Clojure)

Server: Ring

Define a function to handle the request to the ngrok domain.

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})
0.1s
ngrok-client-server-env (Clojure)
#'user/handler #object [user$handler 0x692f03a2 "user$handler@692f03a2"]

Start a Jetty server.

(require '[ring.adapter.jetty :as jetty])
(jetty/run-jetty handler {:port 5000
                          :join? false})
1.6s
ngrok-client-server-env (Clojure)
#object [org.eclipse.jetty.server.Server 0x10f92f53 "Server@10f92f53{STARTED}[9.4.31.v20200723]"]

Open the ngrok URL in another window. My ngrok service is configured to use the subdomain btf (as configured in ngrok.yml above). The result is "Hello World" at http://btf.ngrok.io/. This is only available to my account using my credentials.

If you do not have a paid account, you can simply use the free tunnel service with the dynamically-generated subdomain.

Client

Use clj-http to GET/PUT/POST/DELETE, etc.... Successfully get a website and receive a 200 response.

(require '[clj-http.client :as client])
(:status (client/get "https://archive.org"))
8.0s
ngrok-client-server-env (Clojure)
200

Use cemerick.url to generate a valid URL.

(require '[cemerick.url :refer (url url-encode)])
(str "https://html.duckduckgo.com/html/"
     "&q=" (url-encode "clojure/clojurescript"))
0.4s
ngrok-client-server-env (Clojure)
"https://html.duckduckgo.com/html/&q=clojure%2Fclojurescript"

Get the URL with a query.

(let [url (str "https://html.duckduckgo.com/html/"
               "&q=" (url-encode "clojure/clojurescript"))]
  {:nextjournal/viewer "html"
   :nextjournal.viewer/value (:body (client/get url))})
1.3s
ngrok-client-server-env (Clojure)

Browser

Install firefox-geckodriver to use with Etaoin. Etaoin is a Clojure Webdriver protocol implementation.

apt-get update
apt-get install firefox-geckodriver
50.5s
ngrok-client-server-env (Bash in Clojure)
(require '[etaoin.api :as e])
(def driver (e/firefox {:headless true}))
(e/headless? driver)
7.7s
ngrok-client-server-env (Clojure)
true

Where are we?

(e/go driver "https://nextjournal.com/")
(e/get-title driver)
5.0s
ngrok-client-server-env (Clojure)
"Nextjournal"

Browse to the ngrok endpoint we just built in this notebook. It will return the server response, "Hello World."

(doto driver
  (e/go "http://btf.ngrok.io/")
  (e/screenshot "/results/server.png"))
1.2s
ngrok-client-server-env (Clojure)
Map{:args: List(3), :headless: true, :capabilities: Map, :process: #object [java.lang.ProcessImpl 0x2a2e01fa "Process[pid=1462, exitValue="not exited"]"], :locator: "xpath", :type: :firefox, :env: nil, :port: 35579, :host: "127.0.0.1", :url: "http://127.0.0.1:35579", :session: "0ae385d3-e93c-49aa-8234-a539052e7f95"}

Appendix

This work is licensed under a Creative Commons Attribution 4.0 International License.

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        clj-http {:mvn/version "2.3.0"}
        com.cemerick/url {:mvn/version "0.1.1"}
        ring/ring-core {:mvn/version "1.8.2"}
        ring/ring-jetty-adapter {:mvn/version "1.8.2"}
        etaoin {:mvn/version "0.4.1"}
        compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation
Runtimes (1)