{"id":23070179,"url":"https://github.com/strojure/undertow","last_synced_at":"2025-08-15T13:32:50.369Z","repository":{"id":64982109,"uuid":"578660564","full_name":"strojure/undertow","owner":"strojure","description":"Clojure API to Undertow web server.","archived":false,"fork":false,"pushed_at":"2023-05-12T07:47:14.000Z","size":132,"stargazers_count":14,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"default","last_synced_at":"2024-08-11T11:26:13.077Z","etag":null,"topics":["adapter","clojure","undertow","web-server"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/strojure.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-12-15T15:24:19.000Z","updated_at":"2024-02-16T10:21:49.000Z","dependencies_parsed_at":"2023-02-18T23:15:37.946Z","dependency_job_id":null,"html_url":"https://github.com/strojure/undertow","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strojure%2Fundertow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strojure%2Fundertow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strojure%2Fundertow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strojure%2Fundertow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strojure","download_url":"https://codeload.github.com/strojure/undertow/tar.gz/refs/heads/default","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229916377,"owners_count":18144131,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adapter","clojure","undertow","web-server"],"created_at":"2024-12-16T06:20:04.300Z","updated_at":"2024-12-16T06:20:04.988Z","avatar_url":"https://github.com/strojure.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# undertow\n\nClojure API to Undertow web server.\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.github.strojure/undertow.svg)](https://clojars.org/com.github.strojure/undertow)\n\n[![cljdoc badge](https://cljdoc.org/badge/com.github.strojure/undertow)](https://cljdoc.org/d/com.github.strojure/undertow)\n[![tests](https://github.com/strojure/undertow/actions/workflows/tests.yml/badge.svg)](https://github.com/strojure/undertow/actions/workflows/tests.yml)\n\n## Motivation\n\n- Embrace Undertow API, don't hide Undertow features behind layers of \n  simplifying abstractions.\n- Decouple server configuration and concepts like [ring handlers]\n  or [pedestal interceptors].\n- Extend functionality using Clojure idioms.\n- Reuse Undertow's library of HTTP handlers.\n- Provide declarative description of server configuration.\n- Minimize the impact of implementation on performance.\n- Implement web security recommendations.\n\n## Companion projects\n\n- Ring adapter https://github.com/strojure/ring-undertow.\n\n## Web security\n\nThis library provides implementations:\n\n- Session cookie is `HttpOnly` with `SameSite=Lax` by default.\n\n- The [handler/security] implements:\n  - `Content-SecurityPolicy` response header with `report-uri` handler.\n  - `Strict-Transport-Security` response header.\n  - `Referrer-Policy` response header.\n  - `X-Content-Type-Options` response header (enabled by default).\n\n## Usage\n\nUndertow server can be started using [start][server_start] and stopped\nwith [stop][server_stop]. The running server instance is `java.io.Closeable` so\nit can be used with `with-open` macro.\n\n### Server configuration\n\nThe [start][server_start] function accepts clojure map with options \ntranslated to corresponding calls of Undertow builder methods. The \nconfiguration map structure reflects Undertow’s Java API.\n\nThe minimal configuration has only `:port` and `:handler` keys (Undertow \nwill start even with empty configuration, but it is pretty useless). This \nstarts HTTP listener on 8080 port with default settings.\n\n```clojure\n(server/start {:port 8080 :handler my-handler})\n```\n\n### Listener configuration\n\nListener configuration is defined in a map of ports and listener options \nunder the `:port` key.\n\nSimple HTTP listener:\n\n```clojure\n;; All three are the same:\n{:port 8080}\n{:port {8080 {}}}\n{:port {8080 {:host \"localhost\"}}}\n```\n\nEvery port can use its own handler instead of server’s one.\n\n```clojure\n{:port {8081 {:handler my-handler-1}\n        8082 {:handler my-handler-2}}}\n```\n\nHTTPS listener:\n\n```clojure\n{:port {4242 {:https {:key-managers [] :trust-managers []}}}}\n{:port {4242 {:https {:ssl-context my-ssl-context}}}}\n```\n\nNOTE: The AJP listener type is not available in declarative form.\n\n### Handler configuration\n\nLet’s suppose there is a scenario:\n\n- Webapi handler on the \"webapi.company.com\" host.\n- Application specific handlers on the hosts \"app1.company.com\" and\n  \"app2.company.com\".\n- Static resource handler for app hosts but not for webapi.\n- Websocket handler for app hosts but not for webapi.\n- HTTP sessions for app hosts but not for webapi, websockets and static \n  resources.\n- Some fixed response headers.\n\nThe Undertow handler for this case can be configured in different ways:\n\n```clojure\n(ns usage.handler-configuration\n  (:require [strojure.undertow.handler :as handler]\n            [strojure.undertow.server :as server])\n  (:import (io.undertow.server HttpServerExchange)\n           (io.undertow.util Headers)))\n\n(defn- my-handler\n  [label]\n  (handler/with-exchange\n    (fn [^HttpServerExchange e]\n      (-\u003e (.getResponseSender e)\n          (.send (str \"Dummy handler: \" label))))))\n\n(def ^:private websocket-callback\n  {:on-connect (fn [{:keys [callback exchange channel]}] (comment callback exchange channel))\n   :on-message (fn [{:keys [callback channel text]}] (comment callback channel text))\n   :on-close (fn [{:keys [callback channel code reason]}] (comment callback channel code reason))\n   :on-error (fn [{:keys [callback channel error]}] (comment callback channel error))})\n\n(defn- set-content-type-options\n  [^HttpServerExchange exchange]\n  (let [headers (.getResponseHeaders exchange)]\n    (when (.contains headers Headers/CONTENT_TYPE)\n      (.put headers Headers/X_CONTENT_TYPE_OPTIONS \"nosniff\"))))\n\n(defn imperative-handler-config\n  \"The handler configuration created by invocation of series of handler\n  constructors.\"\n  []\n  ;; The chain of HTTP handler in reverse order.\n  (-\u003e (my-handler :default-handler)\n      ;; The handlers for app hostnames.\n      (handler/virtual-host\n        {:host {\"app1.company.com\" (my-handler :app1-handler)\n                \"app2.company.com\" (my-handler :app2-handler)}})\n      ;; Enable sessions for next handlers (above).\n      (handler/session {})\n      ;; Path specific handlers.\n      (handler/path {:prefix {\"static\" (handler/resource {:resource-manager :classpath-files\n                                                          :prefix \"public/static\"})}\n                     :exact {\"websocket\" (handler/websocket websocket-callback)}})\n      ;; Modify response before commit.\n      (handler/on-response-commit set-content-type-options)\n      ;; Add fixed response headers.\n      (handler/set-response-header {\"X-Frame-Options\" \"DENY\"})\n      ;; The handler for webapi hostname.\n      (handler/virtual-host {:host {\"webapi.company.com\" (my-handler :webapi-handler)}})\n      ;; Supplemental useful handlers.\n      (handler/simple-error-page)\n      (handler/proxy-peer-address)))\n\n(defn symbol-handler-config\n  \"Declarative handler configuration as sequence of chaining handlers which are\n  referred as symbols.\"\n  []\n  [;; Supplemental useful handlers.\n   {:type `handler/proxy-peer-address}\n   {:type `handler/simple-error-page}\n   ;; The handler for webapi hostname.\n   {:type `handler/virtual-host\n    :host {\"webapi.company.com\" (my-handler :webapi-handler)}}\n   ;; Add fixed response headers.\n   {:type `handler/set-response-header :header {\"X-Frame-Options\" \"DENY\"}}\n   ;; Modify response before commit.\n   {:type `handler/on-response-commit :listener set-content-type-options}\n   ;; Path specific handlers.\n   {:type `handler/path\n    :prefix {\"static\" {:type `handler/resource :resource-manager :classpath-files\n                       :prefix \"public/static\"}}\n    :exact {\"websocket\" {:type `handler/websocket :callback websocket-callback}}}\n   ;; Enable sessions for next handlers.\n   {:type `handler/session}\n   ;; The handlers for app hostnames.\n   {:type `handler/virtual-host\n    :host {\"app1.company.com\" (my-handler :app1-handler)\n           \"app2.company.com\" (my-handler :app2-handler)}}\n   ;; Last resort handler\n   (my-handler :default-handler)])\n\n(defn instance-handler-config\n  \"Declarative handler configuration as sequence of chaining handlers which are\n  referred as handler function instances.\"\n  []\n  [;; Supplemental useful handlers.\n   {:type handler/proxy-peer-address}\n   {:type handler/simple-error-page}\n   ;; The handler for webapi hostname.\n   {:type handler/virtual-host\n    :host {\"webapi.company.com\" (my-handler :webapi-handler)}}\n   ;; Add fixed response headers.\n   {:type handler/set-response-header :header {\"X-Frame-Options\" \"DENY\"}}\n   ;; Modify response before commit.\n   {:type handler/on-response-commit :listener set-content-type-options}\n   ;; Path specific handlers.\n   {:type handler/path\n    :prefix {\"static\" {:type handler/resource :resource-manager :classpath-files\n                       :prefix \"public/static\"}}\n    :exact {\"websocket\" {:type handler/websocket :callback websocket-callback}}}\n   ;; Enable sessions for next handlers.\n   {:type handler/session}\n   ;; The handlers for app hostnames.\n   {:type handler/virtual-host\n    :host {\"app1.company.com\" (my-handler :app1-handler)\n           \"app2.company.com\" (my-handler :app2-handler)}}\n   ;; Last resort handler\n   (my-handler :default-handler)])\n\n(defn keyword-handler-config\n  \"Declarative handler configuration as sequence of chaining handlers which are\n  referred as keywords so can be easily stored in EDN file.\"\n  []\n  [;; Supplemental useful handlers.\n   {:type ::handler/proxy-peer-address}\n   {:type ::handler/simple-error-page}\n   ;; The handler for webapi hostname.\n   {:type ::handler/virtual-host\n    :host {\"webapi.company.com\" (my-handler :webapi-handler)}}\n   ;; Add fixed response headers.\n   {:type ::handler/set-response-header :header {\"X-Frame-Options\" \"DENY\"}}\n   ;; Modify response before commit.\n   {:type ::handler/on-response-commit :listener set-content-type-options}\n   ;; Path specific handlers.\n   {:type ::handler/path\n    :prefix {\"static\" {:type ::handler/resource :resource-manager :classpath-files\n                       :prefix \"public/static\"}}\n    :exact {\"websocket\" {:type ::handler/websocket\n                         :callback websocket-callback}}}\n   ;; Enable sessions for next handlers.\n   {:type ::handler/session}\n   ;; The handlers for app hostnames.\n   {:type ::handler/virtual-host\n    :host {\"app1.company.com\" (my-handler :app1-handler)\n           \"app2.company.com\" (my-handler :app2-handler)}}\n   ;; Last resort handler\n   (my-handler :default-handler)])\n\n(comment\n  (with-open [_ (server/start {:handler (imperative-handler-config)})])\n  (with-open [_ (server/start {:handler (symbol-handler-config)})])\n  (with-open [_ (server/start {:handler (instance-handler-config)})])\n  (with-open [_ (server/start {:handler (keyword-handler-config)})])\n  )\n```\n\n[ring handlers]:\nhttps://github.com/ring-clojure/ring/wiki/Concepts#handlers\n\n[pedestal interceptors]:\nhttp://pedestal.io/reference/interceptors\n\n[server_start]:\nhttps://cljdoc.org/d/com.github.strojure/undertow/CURRENT/api/strojure.undertow.server#start\n\n[server_stop]:\nhttps://cljdoc.org/d/com.github.strojure/undertow/CURRENT/api/strojure.undertow.server#stop\n\n[handler/security]:\nhttps://cljdoc.org/d/com.github.strojure/undertow/CURRENT/api/strojure.undertow.handler#security\n\n---\n\nAuthored by [Sergey Trofimov](https://github.com/serioga).\n\n[![license](https://img.shields.io/badge/license-The%20Unlicense-informational)](UNLICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrojure%2Fundertow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrojure%2Fundertow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrojure%2Fundertow/lists"}