{"id":19293939,"url":"https://github.com/igrishaev/ring-jdk-adapter","last_synced_at":"2025-08-21T07:14:13.064Z","repository":{"id":261560541,"uuid":"884358125","full_name":"igrishaev/ring-jdk-adapter","owner":"igrishaev","description":"Zero-deps Ring server on top of jdk.httpserver","archived":false,"fork":false,"pushed_at":"2025-06-04T08:28:44.000Z","size":123,"stargazers_count":36,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-12T16:30:30.735Z","etag":null,"topics":["clojure","http","java","ring"],"latest_commit_sha":null,"homepage":"https://github.com/igrishaev/ring-jdk-adapter","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/igrishaev.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-11-06T15:54:43.000Z","updated_at":"2025-07-02T08:18:30.000Z","dependencies_parsed_at":"2025-04-12T04:27:28.451Z","dependency_job_id":"89405e9b-f025-487f-99c5-79a30c3311de","html_url":"https://github.com/igrishaev/ring-jdk-adapter","commit_stats":null,"previous_names":["igrishaev/ring-jdk-adapter"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/igrishaev/ring-jdk-adapter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fring-jdk-adapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fring-jdk-adapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fring-jdk-adapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fring-jdk-adapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/ring-jdk-adapter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fring-jdk-adapter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271441987,"owners_count":24760351,"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","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["clojure","http","java","ring"],"created_at":"2024-11-09T22:36:38.016Z","updated_at":"2025-08-21T07:14:13.052Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ring JDK Adapter\n\nRing JDK Adapter is a small wrapper on top of a built-in HTTP server available\nin Java. It's like Jetty but has no dependencies. It's almost as fast as Jetty,\ntoo (see benchmars below).\n\n## Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [Why](#why)\n- [Availability](#availability)\n- [Installation](#installation)\n- [Quick Demo](#quick-demo)\n- [Parameters](#parameters)\n- [Body Type](#body-type)\n- [Middleware](#middleware)\n- [Exception Handling](#exception-handling)\n- [HTTPs \u0026 SSL](#https--ssl)\n- [Benchmarks](#benchmarks)\n\n\u003c!-- tocstop --\u003e\n\n## Why\n\nSometimes you want a local HTTP server in Clojure, e.g. for testing or mocking\npurposes. There is a number of adapters for Ring but all of them rely on third\nparty servers like Jetty, Undertow, etc. Running them means to fetch plenty of\ndependencies. This is tolerable to some extent, yet sometimes you really want\nsomething quick and simple.\n\nSince version 9 or 11 (I don't remember for sure), Java ships its own HTTP\nserver. The package name is `com.sun.net.httpserver` and the module name is\n`jdk.httpserver`. The library provides an adapter to serve Ring handlers. It's\ncompletely free from any dependencies.\n\nRing JDK Adapter is a great choice for local HTTP stubs or mock services that\nmimic HTTP services. Despite some people think it's for development purposes\nonly, the server is pretty fast! One can use it even in production.\n\n## Availability\n\nIt's worth mentioning that some Java installations may miss the `jdk.httpserver`\nmodule. Please ensure the JVM you're using in production supports it first. Here\nis the list of the JVMs I've checked manually:\n\n- OpenJDK\n- GraalVM\n\nCheck out the following links:\n\n- [StackOverflow: Is package com.sun.net.httpserver standard?](https://stackoverflow.com/questions/58764710/is-package-com-sun-net-httpserver-standard)\n- [Java® Platform, Standard Edition \u0026 Java Development Kit Version 21 API Specification](https://docs.oracle.com/en/java/javase/21/docs/api/index.html)\n\n## Installation\n\n~~~clojure\n;; lein\n[com.github.igrishaev/ring-jdk-adapter \"0.1.2\"]\n\n;; deps\ncom.github.igrishaev/ring-jdk-adapter {:mvn/version \"0.1.2\"}\n~~~\n\nRequires Java version at least 16, Clojure at least 1.8.0.\n\n## Quick Demo\n\nImport the namespace, declare a Ring handler as usual:\n\n~~~clojure\n(ns demo\n  (:require\n   [ring.adapter.jdk :as jdk]))\n\n(defn handler [request]\n  {:status 200\n   :headers {\"Content-Type\" \"text/plain\"}\n   :body \"Hello world!\"})\n~~~\n\nPass it into the `server` function and check the http://127.0.0.1:8082 page in\nyour browser:\n\n~~~clojure\n(def server\n  (jdk/server handler {:port 8082}))\n~~~\n\nThe `server` function returns an instance of the `Server` class. To stop it,\npass the result into the `jdk/stop` or `jdk/close` functions:\n\n~~~clojure\n(jdk/stop server)\n~~~\n\nSince the `Server` class implements `AutoCloseable` interface, it's compatible\nwith the `with-open` macro:\n\n~~~clojure\n(with-open [server (jdk/server handler opt?)]\n  ...)\n~~~\n\nThe server gets closed once you've exited the macro. Here is a similar\n`with-server` macro which acts the same:\n\n~~~clojure\n(jdk/with-server [handler opt?]\n  ...)\n~~~\n\n## Parameters\n\nThe `server` function and the `with-server` macro accept the second optional map\nof the parameters:\n\n| Name              | Default   | Description                                                                   |\n|-------------------|-----------|-------------------------------------------------------------------------------|\n| `:host`           | 127.0.0.1 | Host name to listen                                                           |\n| `:port`           | 8080      | Port to listen                                                                |\n| `:stop-delay-sec` | 0         | How many seconds to wait when stopping the server                             |\n| `:root-path`      | /         | A path to mount the handler                                                   |\n| `:threads`        | 0         | Amount of CPU threads. When \u003e thn 0, a new `FixedThreadPool` executor is used |\n| `:executor`       | null      | A custom instance of `Executor`. Might be a virtual executor as well          |\n| `:socket-backlog` | 0         | A numeric value passed into the `HttpServer.create` method                    |\n\nExample:\n\n~~~clojure\n(def server\n  (jdk/server handler\n              {:host \"0.0.0.0\" ;; listen all addresses\n               :port 8800      ;; a custom port\n               :threads 8      ;; use custom fixed thread executor\n               :root-path \"/my/app\"}))\n~~~\n\nWhen run, the handler above is be available by the address\nhttp://127.0.0.1:8800/my/app in the browser.\n\n## Body Type\n\nJDK adapter supports the following response `:body` types:\n\n- `java.lang.String`\n- `java.io.InputStream`\n- `java.io.File`\n- `java.lang.Iterable\u003c?\u003e` (see below)\n- `null` (nothing gets sent)\n\nWhen the body is `Iterable` (might be a lazy seq as well), every item is sent as\na string in UTF-8 encoding. Null values are skipped.\n\n## Middleware\n\nTo gain all the power of Ring (parsed parameters, JSON, sessions, etc), wrap\nyour handler with the standard middleware:\n\n~~~clojure\n(ns demo\n  (:require\n    [ring.middleware.params :refer [wrap-params]]\n    [ring.middleware.keyword-params :refer [wrap-keyword-params]]\n    [ring.middleware.multipart-params :refer [wrap-multipart-params]]))\n\n(let [handler (-\u003e handler\n                  wrap-keyword-params\n                  wrap-params\n                  wrap-multipart-params)]\n  (jdk/server handler {:port 8082}))\n~~~\n\nThe wrapped handler will receive a `request` map with parsed `:query-params`,\n`:form-params`, and `:params` fields. These middleware come from the `ring-core`\nlibrary which you need to add into your dependencies. The same applies to\nhandling JSON and the `ring-json` library.\n\n## Exception Handling\n\nIf something gets wrong while handling a request, you'll get a plain text page\nwith a short message and a stack trace:\n\n~~~clojure\n(defn handler [request]\n  (/ 0 0) ;; !\n  {:status 200\n   :headers {\"Content-Type\" \"text/plain\"}\n   :body \"hello\"})\n~~~\n\nThis is what you'll get in the browser:\n\n~~~text\nfailed to execute ring handler\njava.lang.ArithmeticException: Divide by zero\n\tat clojure.lang.Numbers.divide(Numbers.java:190)\n\tat clojure.lang.Numbers.divide(Numbers.java:3911)\n\tat bench$handler.invokeStatic(form-init14855917186251843338.clj:8)\n\tat bench$handler.invoke(form-init14855917186251843338.clj:7)\n\tat ring.adapter.jdk.Handler.handle(Handler.java:112)\n\tat jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)\n\tat jdk.httpserver/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)\n\tat jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)\n\tat jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:873)\n\tat jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)\n\tat jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:849)\n\tat jdk.httpserver/sun.net.httpserver.ServerImpl$DefaultExecutor.execute(ServerImpl.java:204)\n\tat jdk.httpserver/sun.net.httpserver.ServerImpl$Dispatcher.handle(ServerImpl.java:567)\n\tat jdk.httpserver/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:532)\n\tat java.base/java.lang.Thread.run(Thread.java:1575)\n~~~\n\nTo prevent this data from being leaked to the client, use your own\n`wrap-exception` middleware, something like this:\n\n~~~clojure\n(defn wrap-exception [handler]\n  (fn [request]\n    (try\n      (handler request)\n      (catch Exception e\n        (log/errorf e ...)\n        {:status 500\n         :headers {...}\n         :body \"No cigar! Roll again!\"}))))\n~~~\n\n## HTTPs \u0026 SSL\n\nAt the moment, the adapter supports HTTP only. There is a pending TODO to make\nit also work with HTTPs and a custom SSL context.\n\n## Benchmarks\n\nAs mentioned above, the JDK server although though is for dev purposes only, is\nnot so bad! The chart below proves it's almost as fast as Jetty. There are five\nattempts of `ab -l -n 1000 -c 50 ...` made against both Jetty and JDK servers\n(1000 requests in total, 50 parallel). The levels of RPS are pretty equal: about\n12-13K requests per second.\n\nMeasured on Macbook M3 Pro 32Gb, default settings, the same REPL.\n\n\u003cimg src=\"media/chart_1.svg\" width=75% height=auto\u003e\n\nIvan Grishaev, 2024\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fring-jdk-adapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fring-jdk-adapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fring-jdk-adapter/lists"}