HTTP forwarding & codecs
Forward a native response, edit its status and headers, then decode its body as text.
Build and run
Requires Java 21+, Clojure CLI and the runtime named below. The example pins the compiler that supports native ^:async and await. Run these commands in a separate terminal from other examples.
git clone https://github.com/OUTCASTGEEK-TECH/fast-twitch.git
cd fast-twitch
git checkout a4eb73f96836d034c7a24fe09dc7d51e4c581f64
cd examples/07-native-transports
clojure -M:build http
node target/http.cjs
What happens
The program creates /origin and /forward routes and starts a listener on port 0. A readiness Promise receives the actual address from :on-listen, so there is no arbitrary sleep or hardcoded unused port. /forward requests /origin with the default native codec, then changes the returned map's status to 201 and adds x-forwarded: yes without collecting its body.
The outer request uses :codec :text, collecting the final body. The assertions require status 201, the forwarding header and "origin body". Successful completion prints HTTP forwarding and map edits passed and exits after server/close!.
Read the boundaries
routing/routes still uses ordinary route maps. server/listen! accepts that Ring handler directly. client/request! returns a response map carrying the native body; editing the map does not call .text on that stream. Only the outer text-codec request consumes it. The base and readiness atoms belong to this example invocation, not to all HTTP requests in an application.
Exercise
Add a response header in /origin and verify that /forward preserves it. Change the outer codec to :bytes and inspect the Uint8Array. Set a decoded body limit below the origin body's byte size and inspect the rejection; do not attempt to reuse the consumed response after that failure.
HTTP options and codecs · Client API.
Complete source and build files
The files below are copied without abbreviation from Fast-Twitch revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64. The walkthrough and API reference use the same revision.
Source: examples/07-native-transports/src/native_demo/http.cljs
View this exact file on GitHub.
(ns native-demo.http
(:require [cljs.core :refer [await]]
[fast-twitch.routing :as routing]
[fast-twitch.client.core :as client]
[fast-twitch.server.core :as server])
(:refer-global :only [Promise console]))
(defn ^:async -main
[]
(let [base (atom nil)
listened (atom nil)
ready (Promise. (fn [resolve _]
(reset! listened resolve)))
handler (routing/routes [{:pattern "/origin"
:handler (fn [_]
(routing/response "origin body"))}
{:pattern "/forward"
:handler
(^:async fn
[_]
(let [response (await (client/request!
{:url (str @base "/origin")
:request-method :get
:headers {}}))]
(-> response
(assoc :status 201)
(assoc-in [:headers "x-forwarded"] "yes"))))}]
(fn [_]
(routing/not-found "missing")))
serving (server/listen! handler {:port 0 :on-listen @listened})]
(try (reset! base (str "http://127.0.0.1:" (.-port (await ready))))
(let [response (await (client/request! {:url (str @base "/forward")
:request-method :get
:headers {}}
{:codec :text}))]
(assert (= 201 (:status response)))
(assert (= "yes" (get-in response [:headers "x-forwarded"])))
(assert (= "origin body" (:body response)))
(.log console "HTTP forwarding and map edits passed"))
(finally (await (server/close! serving {:force true}))))))
(set! *main-cli-fn* -main)
Source: examples/07-native-transports/deps.edn
View this exact file on GitHub.
{:aliases {:build {:main-opts ["-m" "native-demo.build"]}}
:deps {fast-twitch/fast-twitch {:local/root "../.."}
org.clojure/clojurescript {:git/sha "56a94ac13bfac22108e4d868dfd4763edaceb864"
:git/url "https://github.com/clojure/clojurescript"}}
:paths ["src" "dev"]}
Source: examples/07-native-transports/dev/native_demo/build.clj
View this exact file on GitHub.
(ns native-demo.build
(:require [cljs.build.api :as cljs]))
(def modes
#{"http" "websocket" "sse" "streams" "ports" "tcp-node" "tcp-deno" "tcp-bun"})
(defn -main
[mode]
(doseq [mode (if (= mode "all") (sort modes) [mode])]
(when-not (modes mode) (throw (ex-info "Choose an example" {:modes modes})))
(cljs/build "src"
{:main (symbol (str "native-demo." mode))
:target :nodejs
:output-to (str "target/" mode ".cjs")
:output-dir (str "target/" mode)
:optimizations :simple})
(println mode "COMPILED")))