Set up a http server, and other system boilerplate

This commit is contained in:
Arne Brasseur 2022-12-04 10:01:37 +01:00
parent c8b0d4e686
commit 39fe19f7a3
17 changed files with 5770 additions and 8 deletions

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# Souk - ActivityPub in Clojure
A work-in-progress implementation of the ActivityPub standard in Clojure.
This repo should primarily be understood as a byproduct of the live streams
which explore this topic, it is not meant to be generally consumable, either as
a library or an application.

View file

@ -1,6 +1,28 @@
{:deps {hato/hato {:mvn/version "0.9.0"}
cheshire/cheshire {:mvn/version "5.11.0"}
seancorfield/next.jdbc {:mvn/version "1.2.659"}
com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"}
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
}}
;; Application setup
com.lambdaisland/webbing {:local/root "/home/arne/github/lambdaisland/webbing"}
;; {:mvn/version "0.4.20-alpha"}
;; Incoming HTTP
ring/ring-core {:mvn/version "1.9.6"}
ring/ring-jetty-adapter {:mvn/version "1.9.6"}
ring/ring-mock {:mvn/version "0.4.0"}
metosin/malli {:mvn/version "0.9.2"}
metosin/muuntaja {:mvn/version "0.6.8"}
metosin/reitit {:mvn/version "0.5.18"}
;; Outgoing HTTP
hato/hato {:mvn/version "0.9.0"}
;; Formats
cheshire/cheshire {:mvn/version "5.11.0"}
;; Database
seancorfield/next.jdbc {:mvn/version "1.2.659"}
com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"}
}
:aliases
{:dev {:extra-paths ["dev"]}
:souk {:main-opts ["-m" "lambdaisland.souk"]}}}

3
dev/user.clj Normal file
View file

@ -0,0 +1,3 @@
(ns user)
(set! *print-namespace-maps* false)

BIN
preview-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

5507
preview-image.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 434 KiB

View file

@ -2,7 +2,6 @@
(:require [lambdaisland.souk.json-ld :as ld]
[lambdaisland.souk.activitypub :refer :all]))
(ld/expand (:body (ld/json-get "https://raw.githubusercontent.com/gobengo/activitystreams2-spec-scraped/master/data/activitystreams-vocabulary/1528589057.json")))
(let [{:owl/keys [imports]} (GET "https://raw.githubusercontent.com/gobengo/activitystreams2-spec-scraped/master/data/activitystreams-vocabulary/1528589057.json")]

View file

@ -0,0 +1,8 @@
{:http/router
{:gx/component lambdaisland.souk.components.router/component
:gx/props {:dev-router? #setting :dev/reload-routes?}}
:http/server
{:gx/component lambdaisland.souk.components.jetty/component
:gx/props {:jetty-options {:port #setting :port}
:router (gx/ref :http/router)}}}

View file

@ -0,0 +1 @@
{:dev/reload-routes? true}

View file

@ -0,0 +1 @@
{:port 80}

View file

@ -0,0 +1,2 @@
{:port 3444
:dev/reload-routes? false}

View file

@ -0,0 +1,6 @@
(ns lambdaisland.souk
(:require [lambdaisland.webbing.prod :as prod]
[lambdaisland.souk.setup :as setup]))
(defn -main [& _]
(prod/go (setup/prod-setup)))

View file

@ -1,8 +1,7 @@
(ns lambdaisland.souk.activitypub
"Interact with ActivityPub instances"
(:require [lambdaisland.souk.json-ld :as ld]))
(set! *print-namespace-maps* false)
(def common-prefixes
{"dcterms" "http://purl.org/dc/terms/"
"ldp" "http://www.w3.org/ns/ldp#"

View file

@ -0,0 +1,93 @@
(ns lambdaisland.souk.components.jetty
(:require [ring.adapter.jetty :as ring.jetty]
[reitit.ring :as reitit-ring])
(:import (org.eclipse.jetty.server Server)))
(def defaults {:join? false})
(def ?JettyOptions
"Start a Jetty webserver to serve the given handler according to the
supplied options:
:configurator - a function called with the Jetty Server instance
:async? - if true, treat the handler as asynchronous
:async-timeout - async context timeout in ms
(defaults to 0, no timeout)
:async-timeout-handler - an async handler to handle an async context timeout
:port - the port to listen on (defaults to 80)
:host - the hostname to listen on
:join? - blocks the thread until server ends
(defaults to true)
:daemon? - use daemon threads (defaults to false)
:http? - listen on :port for HTTP traffic (defaults to true)
:ssl? - allow connections over HTTPS
:ssl-port - the SSL port to listen on (defaults to 443, implies)
:ssl? is true
:ssl-context - an optional SSLContext to use for SSL connections
:exclude-ciphers - when :ssl? is true, additionally exclude these
cipher suites
:exclude-protocols - when :ssl? is true, additionally exclude these
protocols
:replace-exclude-ciphers? - when true, :exclude-ciphers will replace rather
than add to the cipher exclusion list (defaults)
to false
:replace-exclude-protocols? - when true, :exclude-protocols will replace
rather than add to the protocols exclusion list
(defaults to false)
:keystore - the keystore to use for SSL connections
:keystore-type - the keystore type (default jks)
:key-password - the password to the keystore
:keystore-scan-interval - if not nil, the interval in seconds to scan for an
updated keystore
:thread-pool - custom thread pool instance for Jetty to use
:truststore - a truststore to use for SSL connections
:trust-password - the password to the truststore
:max-threads - the maximum number of threads to use (default 50)
:min-threads - the minimum number of threads to use (default 8)
:max-queued-requests - the maximum number of requests to be queued
:thread-idle-timeout - Set the maximum thread idle time. Threads that are
idle for longer than this period may be stopped
(default 60000)
:max-idle-time - the maximum idle time in milliseconds for a
connection (default 200000)
:client-auth - SSL client certificate authenticate, may be set to
:need,:want or :none (defaults to :none)
:send-date-header? - add a date header to the response (default true)
:output-buffer-size - the response body buffer size (default 32768)
:request-header-size - the maximum size of a request header (default 8192)
:response-header-size - the maximum size of a response header (default 8192)
:send-server-version? - add Server header to HTTP response (default true)"
[:map
[:port pos-int?]
[:host {:optional true} string?]
[:max-threads {:optional true} pos-int?]
[:min-threads {:optional true} pos-int?]
[:max-queued-requests {:optional true} pos-int?]])
(def ?PropsSchema
[:map
[:jetty-options ?JettyOptions]])
(defn ring-handler [reitit-router]
(reitit-ring/ring-handler
reitit-router
(reitit-ring/routes
(reitit-ring/create-default-handler))))
(defn http-start [{:keys [props]}]
(let [{:keys [router jetty-options]} props
jetty-options (merge defaults jetty-options)]
(ring.jetty/run-jetty (ring-handler router) jetty-options)))
(defn http-stop [{^Server this :value}]
(.stop this))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(def component
{:gx/start {:gx/processor http-start
:gx/props-schema ?PropsSchema}
:gx/stop {:gx/processor http-stop}})
(comment
{:gx/component k16.gx.contrib.jetty/component
:gx/props {:jetty-options {}
:handler '(gx/ref :handler)}})

View file

@ -0,0 +1,48 @@
(ns lambdaisland.souk.components.router
"Reitit routes and router"
(:require
[lambdaisland.souk.dev-router :as dev-router]
[muuntaja.core :as muuntaja]
[reitit.dev.pretty :as pretty]
[reitit.ring :as reitit-ring]
[reitit.ring.coercion :as reitit-coercion]
[reitit.ring.middleware.muuntaja :as reitit-muuntaja]
[reitit.ring.middleware.parameters :as reitit-parameters]))
(defn routes [opts]
[["/"
{:get
{:handler
(fn [req]
{:status 200
:body "OK!"})}}]])
(defn wrap-request-context [handler ctx]
(fn [req]
(handler
(assoc req :souk/ctx ctx))))
(defn create-router
[props]
(reitit.ring/router
(into [] (remove nil? (routes props)))
{:exception pretty/exception
:data {:muuntaja muuntaja/instance
:middleware [;; ↓↓↓ request passes through middleware top-to-bottom ↓↓↓
reitit-parameters/parameters-middleware
reitit-muuntaja/format-negotiate-middleware
reitit-muuntaja/format-response-middleware
reitit-muuntaja/format-request-middleware
reitit-coercion/coerce-response-middleware
reitit-coercion/coerce-request-middleware
[wrap-request-context props]
;; ↑↑↑ response passes through middleware bottom-to-top ↑↑↑
]}}))
(defn start! [{:keys [props]}]
(if (:dev-router? props)
(dev-router/dev-router #(create-router props))
(create-router props)))
(def component
{:gx/start {:gx/processor start!}})

View file

@ -0,0 +1,19 @@
(ns lambdaisland.souk.dev-router
"Reitit router wrapper that auto-rebuilds, for REPL workflows."
(:require [reitit.core :as reitit]))
(defn dev-router
"Given a function which builds a reitit router, returns a router which rebuilds
the router on every call. This makes sure redefining routes in a REPL works as
expected. Should only every be used in development mode, since it completely
undoes all of reitit's great performance."
[new-router]
(reify reitit/Router
(router-name [_] (reitit/router-name (new-router)))
(routes [_] (reitit/routes (new-router)))
(compiled-routes [_] (reitit/compiled-routes (new-router)))
(options [_] (reitit/options (new-router)))
(route-names [_] (reitit/route-names (new-router)))
(match-by-path [_ path] (reitit/match-by-path (new-router) path))
(match-by-name [_ name] (reitit/match-by-name (new-router) name))
(match-by-name [_ name path-params] (reitit/match-by-name (new-router) name path-params))))

View file

@ -1,4 +1,6 @@
(ns lambdaisland.souk.json-ld
"Interfacing with JSON-LD endpoints, and converting to and from idiomatic
Clojure data."
(:require [hato.client :as hato]
[clojure.string :as str]
[clojure.walk :as walk]))

View file

@ -0,0 +1,44 @@
(ns lambdaisland.souk.setup
(:require [lambdaisland.webbing.config :as config]
[clojure.java.io :as io]))
(def project 'lambdaisland/souk)
(def start-keys #{:http/server})
(def schemas
{:settings [[:port int?]
[:dev/reload-routes? boolean?]]
:secrets []})
(defn proj-resource [path]
(io/resource (str project "/" path)))
(defn prod-setup []
{:schemas schemas
:keys start-keys
:sources {:config [(proj-resource "config.edn")]
:secrets [(config/cli-args)
(config/env)]
:settings [(config/cli-args)
(config/env)
(config/default-value)
(proj-resource "settings-prod.edn")
(proj-resource "settings.edn")]}})
(defn dev-setup []
(let [local-file (io/file "config.local.edn")
local-config (when (.exists local-file)
(read-string (slurp local-file)))]
{:schemas schemas
:keys (:dev/start-keys local-config start-keys)
:sources {:config [(proj-resource "config.edn")
(proj-resource "config-development.edn")
(dissoc local-config :dev/start-keys)]
:secrets [(config/dotenv)
(config/env)
(config/default-value)]
:settings [(config/dotenv)
(config/env)
(config/default-value)
(proj-resource "settings-dev.edn")
(proj-resource "settings.edn")]}}))