{"id":19674000,"url":"https://github.com/robaho/httpserver","last_synced_at":"2025-04-29T01:31:03.647Z","repository":{"id":204329784,"uuid":"705291690","full_name":"robaho/httpserver","owner":"robaho","description":"a lightweight, ultra-performant, zero dependency JDK http server implementation designed for embedding and optimized for virtual threads","archived":false,"fork":false,"pushed_at":"2025-04-04T15:41:05.000Z","size":518,"stargazers_count":32,"open_issues_count":6,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-04T16:36:24.065Z","etag":null,"topics":["embedded","http","http-server","http2","http2-server","https","httpserver","lightweight","virtual-threads","websocket","websocket-server","websockets"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robaho.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2023-10-15T15:56:58.000Z","updated_at":"2025-04-04T15:41:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"653bc5b5-bfd5-4cb3-9b2e-de3769406ff5","html_url":"https://github.com/robaho/httpserver","commit_stats":null,"previous_names":["robaho/httpserver"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robaho%2Fhttpserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robaho%2Fhttpserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robaho%2Fhttpserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robaho%2Fhttpserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robaho","download_url":"https://codeload.github.com/robaho/httpserver/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251415690,"owners_count":21585874,"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":["embedded","http","http-server","http2","http2-server","https","httpserver","lightweight","virtual-threads","websocket","websocket-server","websockets"],"created_at":"2024-11-11T17:16:56.486Z","updated_at":"2025-04-29T01:31:03.634Z","avatar_url":"https://github.com/robaho.png","language":"Java","readme":"\n# httpserver\n\nZero-dependency implementation of the JDK `com.sun.net.httpserver.HttpServer` [specification](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.httpserver/com/sun/net/httpserver/package-summary.html) with a few significant enhancements.\n\n- WebSocket support using modified source code from nanohttpd.\n- Server-side proxy support using [ProxyHandler](https://github.com/robaho/httpserver/blob/main/src/main/java/robaho/net/httpserver/extras/ProxyHandler.java). (Tunneling proxies are also supported using CONNECT for https.)\n- HTTP/2 [RFC 9113](https://www.rfc-editor.org/rfc/rfc9113.html) support\n- Performance enhancements such as proper HTTP pipelining, optimized String parsing, etc.\n\nAll async functionality has been removed. All synchronized blocks were removed in favor of other Java concurrency concepts.\n\nThe end result is an implementation that easily integrates with Virtual Threads available in JDK 21 - simply set a virtual thread based ExecutorService.\n\nImproves performance by more than **10x** over the JDK implementation.\n\nDesigned for embedding with only a 200kb jar and zero dependencies.\n\n## background\n\nThe built-in JDK httpserver implementation has no support for connection upgrades, so it is not possible to add websocket support.\n\nAdditionally, the code still has a lot of async - e.g. using SSLEngine to provide SSL support - which makes it more difficult to understand and enhance.\n\nThe thread-per-connection synchronous design simplifies the code substantially.\n\n## testing/compliance\n\nNearly all tests from the JDK are included, so this version should be highly compliant and reliable.\n\nAdditional proxy and websockets tests are included.\n\nThe http2 implementation passes all specification tests in [h2spec](https://github.com/summerwind/h2spec)\n\n## maven \n[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.github.robaho/httpserver)\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.robaho\u003c/groupId\u003e\n  \u003cartifactId\u003ehttpserver\u003c/artifactId\u003e\n  \u003cversion\u003euse version from badge above without leading v\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## using\n\nThe JDK will automatically use `robaho.net.httpserver.DefaultHttpServerProvider` instead of the JDK implementation when the jar is placed on the class/module path. If there are multiple `HttpServer` providers on the classpath, the `com.sun.net.httpserver.HttpServerProvider` system property can be used to specify the correct one:\n\nEg. \u003ccode\u003e-Dcom.sun.net.httpserver.HttpServerProvider=robaho.net.httpserver.DefaultHttpServerProvider\u003c/code\u003e\n\nAlternatively, you can instantiate the server directly using [this](https://github.com/robaho/httpserver/blob/main/src/main/java/robaho/net/httpserver/DefaultHttpServerProvider.java#L33).\n\n### Example Usage\n```java\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\n\nimport com.sun.net.httpserver.HttpExchange;\nimport com.sun.net.httpserver.HttpHandler;\nimport com.sun.net.httpserver.HttpServer;\n\npublic class Test {\n\n  public static void main(String[] args) throws Exception {\n    HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);\n    server.createContext(\"/\", new MyHandler());\n    server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); // sets virtual thread executor\n    server.start();\n    }\n\n  static class MyHandler implements HttpHandler {\n    @Override\n    public void handle(HttpExchange exchange) throws IOException {\n      String response = \"This is the response\";\n      byte[] bytes = response.getBytes();\n\n      // -1 means no content, 0 means unknown content length\n      var contentLength = bytes.length == 0 ? -1 : bytes.length;\n\n      try (OutputStream os = exchange.getResponseBody()) {\n        exchange.sendResponseHeaders(200, contentLength);\n        os.write(bytes);\n      }\n    }\n  }\n}\n\n```\nThere is a [simple file server](https://github.com/robaho/httpserver/blob/72775986b38120b30dc4bc0438d21136ff8ec192/src/test/extras/SimpleFileServer.java#L48) that can be used to for basic testing. It has download, echo, and \"hello\" capabilities. Use\n\n```\ngradle runSimpleFileServer\n```\n\n## logging\n\nAll logging is performed using the [Java System Logger](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/System.Logger.html)\n\n## enable Http2\n\nHttp2 support is enabled via Java system properties.\n\nUse `-Drobaho.net.httpserver.http2OverSSL=true` to enable Http2 only via SSL connections.\n\nUse `-Drobaho.net.httpserver.http2OverNonSSL=true` to enable Http2 on Non-SSL connections (which requires prior knowledge). The Http2 upgrade mechanism was deprecated in RFC 9113 so it is not supported.\n\nSee the additional Http2 options in `ServerConfig.java`\n\n## performance\n\nThis version performs more than **10x** faster than the JDK version when tested using the [Tech Empower Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/httpserver) on an identical hardware/work setup with the same JDK 23 version.\u003csup\u003e1\u003c/sup\u003e\n\nThe frameworks were also tested using [go-wrk](https://github.com/tsliwowicz/go-wrk)\u003csup\u003e2\u003c/sup\u003e\n\n\u003csup\u003e1\u003c/sup\u003e_The robaho version has been [submitted](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/httpserver-robaho) to the Tech Empower benchmarks project for 3-party confirmation._\u003cbr\u003e\n\u003csup\u003e2\u003c/sup\u003e_`go-wrk` does not use http pipelining so, the large number of connections is the limiting factor._\n\nPerformance tests against the latest Jetty version were run. The `robaho httpserver` outperformed the Jetty http2 by 3x, and http1 by 5x.\n\nThe Javalin/Jetty project is available [here](https://github.com/robaho/javalin-http2-example)\n\n\u003cdetails\u003e\n    \u003csummary\u003evs JDK performance details\u003c/summary\u003e\n\n**robaho tech empower**\n```\nrobertengels@macmini go-wrk % wrk -H 'Host: imac' -H 'Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7' -H 'Connection: keep-alive' --latency -d 60 -c 64 --timeout 8 -t 2 http://imac:8080/plaintext -s ~/pipeline.lua -- 16\nRunning 1m test @ http://imac:8080/plaintext\n  2 threads and 64 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     1.20ms    9.22ms 404.09ms   85.37%\n    Req/Sec   348.78k    33.28k  415.03k    71.46%\n  Latency Distribution\n     50%    0.98ms\n     75%    1.43ms\n     90%    0.00us\n     99%    0.00us\n  41709198 requests in 1.00m, 5.52GB read\nRequests/sec: 693983.49\nTransfer/sec:     93.98MB\n```\n\n**jdk 23 tech empower**\n```\nrobertengels@macmini go-wrk % wrk -H 'Host: imac' -H 'Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7' -H 'Connection: keep-alive' --latency -d 60 -c 64 --timeout 8 -t 2 http://imac:8080/plaintext -s ~/pipeline.lua -- 16\nRunning 1m test @ http://imac:8080/plaintext\n  2 threads and 64 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.91ms   12.01ms 405.70ms   63.71%\n    Req/Sec   114.30k    18.07k  146.91k    87.10%\n  Latency Distribution\n     50%    4.06ms\n     75%    0.00us\n     90%    0.00us\n     99%    0.00us\n  13669748 requests in 1.00m, 1.72GB read\nRequests/sec: 227446.87\nTransfer/sec:     29.28MB\n\n```\n\n**robaho go-wrk**\n```\nrobertengels@macmini go-wrk % ./go-wrk -c=1024 -d=30 -T=100000 http://imac:8080/plaintext\nRunning 30s test @ http://imac:8080/plaintext\n  1024 goroutine(s) running concurrently\n3252278 requests in 30.118280233s, 387.70MB read\nRequests/sec:\t\t107983.52\nTransfer/sec:\t\t12.87MB\nOverall Requests/sec:\t105891.53\nOverall Transfer/sec:\t12.62MB\nFastest Request:\t83µs\nAvg Req Time:\t\t9.482ms\nSlowest Request:\t1.415359s\nNumber of Errors:\t0\n10%:\t\t\t286µs\n50%:\t\t\t1.018ms\n75%:\t\t\t1.272ms\n99%:\t\t\t1.436ms\n99.9%:\t\t\t1.441ms\n99.9999%:\t\t1.442ms\n99.99999%:\t\t1.442ms\nstddev:\t\t\t35.998ms\n```\n\n**jdk 23 go-wrk**\n```\nrobertengels@macmini go-wrk % ./go-wrk -c=1024 -d=30 -T=100000 http://imac:8080/plaintext\nRunning 30s test @ http://imac:8080/plaintext\n  1024 goroutine(s) running concurrently\n264198 requests in 30.047154195s, 29.73MB read\nRequests/sec:\t\t8792.78\nTransfer/sec:\t\t1013.23KB\nOverall Requests/sec:\t8595.99\nOverall Transfer/sec:\t990.55KB\nFastest Request:\t408µs\nAvg Req Time:\t\t116.459ms\nSlowest Request:\t1.930495s\nNumber of Errors:\t0\n10%:\t\t\t1.166ms\n50%:\t\t\t1.595ms\n75%:\t\t\t1.725ms\n99%:\t\t\t1.827ms\n99.9%:\t\t\t1.83ms\n99.9999%:\t\t1.83ms\n99.99999%:\t\t1.83ms\nstddev:\t\t\t174.373ms\n\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n    \u003csummary\u003evs Jetty performance details\u003c/summary\u003e\n\nThe server is an iMac 4ghz quad-core i7 running OSX 13.7.2. JVM used is JDK 23.0.1. The `h2load` client was connected via a 20Gbs lightening network from an M1 Mac Mini.\n\nUsing `h2load -n 1000000 -m 1000 -c 16 [--h1] http://imac:\u003cport\u003e` \n\nJetty jetty-11.0.24\nJavalin version 6.4.0\n\nJetty 11 http2\n```\nstarting benchmark...\nspawning thread #0: 16 total client(s). 1000000 total requests\nApplication protocol: h2c\nfinished in 3.47s, 288284.80 req/s, 10.17MB/s\nrequests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout\nstatus codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx\ntraffic: 35.29MB (37002689) total, 7.63MB (8001809) headers (space savings 90.12%), 10.49MB (11000000) data\n                     min         max         mean         sd        +/- sd\ntime for request:       94us    381.85ms      6.42ms     21.51ms    96.90%\ntime for connect:      389us      5.88ms      3.15ms      1.75ms    62.50%\ntime to 1st byte:     6.61ms     11.74ms      7.85ms      1.24ms    87.50%\nreq/s           :   18020.94    23235.01    19829.09     1588.94    75.00%\n\n```\n\nJetty 11 http1\n```\nstarting benchmark...\nspawning thread #0: 16 total client(s). 1000000 total requests\nApplication protocol: http/1.1\nfinished in 3.63s, 275680.69 req/s, 36.02MB/s\nrequests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout\nstatus codes: 1000021 2xx, 0 3xx, 0 4xx, 0 5xx\ntraffic: 130.65MB (137000000) total, 86.78MB (91000000) headers (space savings 0.00%), 10.49MB (11000000) data\n                     min         max         mean         sd        +/- sd\ntime for request:     1.59ms    336.00ms     53.17ms     51.56ms    85.36%\ntime for connect:      422us      2.57ms      1.54ms       632us    62.50%\ntime to 1st byte:     2.98ms    314.97ms     26.14ms     77.12ms    93.75%\nreq/s           :   17232.15    21230.14    18780.35     1130.32    68.75\n\n```\n\nrobaho http2\n```\nstarting benchmark...\nspawning thread #0: 16 total client(s). 1000000 total requests\nApplication protocol: h2c\nfinished in 1.03s, 966710.36 req/s, 40.57MB/s\nrequests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout\nstatus codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx\ntraffic: 41.96MB (44000480) total, 5.72MB (6000000) headers (space savings 76.92%), 10.49MB (11000000) data\n                     min         max         mean         sd        +/- sd\ntime for request:      457us     71.41ms     14.71ms      8.63ms    73.09%\ntime for connect:      336us      5.77ms      3.13ms      1.73ms    62.50%\ntime to 1st byte:     6.59ms     15.30ms     10.40ms      3.32ms    50.00%\nreq/s           :   60461.71    66800.04    62509.79     1544.65    75.00%\n```\n\nrobaho http1\n```\nstarting benchmark...\nspawning thread #0: 16 total client(s). 1000000 total requests\nApplication protocol: http/1.1\nfinished in 776.64ms, 1287592.88 req/s, 106.83MB/s\nrequests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout\nstatus codes: 1000123 2xx, 0 3xx, 0 4xx, 0 5xx\ntraffic: 82.97MB (87000000) total, 46.73MB (49000000) headers (space savings 0.00%), 10.49MB (11000000) data\n                     min         max         mean         sd        +/- sd\ntime for request:      376us    380.30ms      9.12ms     32.43ms    99.20%\ntime for connect:      240us      2.51ms      1.50ms       720us    62.50%\ntime to 1st byte:     3.04ms     18.85ms      8.93ms      5.77ms    68.75%\nreq/s           :   80530.13   167605.46   122588.82    42385.59    87.50%\n```\n\n\u003c/details\u003e\n\n\n## server statistics\n\nThe server tracks some basic statistics. To enable the access endpoint `/__stats`, set the system property `robaho.net.httpserver.EnableStatistics=true`.\n\nSample usage:\n\n```shell\n$ curl http://localhost:8080/__stats\nConnections: 4264\nActive Connections: 2049\nRequests: 2669256\nRequests/sec: 73719\nHandler Exceptions: 0\nSocket Exceptions: 0\nMac Connections Exceeded: 0\nIdle Closes: 0\nReply Errors: 0\n```\n\nThe counts and rates for non \"Total\" statistics are reset with each pull of the statistics.\n\n## performance notes\n\nHttp2 performance has not been fully optimized. The http2 version is about 20-30% slower than http1. I expect this to be the case with most http2 implementations due to the complexity.\nhttp2 outperforms http1 when sending multiple simultaneous requests from the client with payloads, as most servers and clients do not implement http pipelining when payloads are involved.\n\nTODO: sending hpack headers does not use huffman encoding or dynamic table management. see the following paper https://www.mew.org/~kazu/doc/paper/hpack-2017.pdf for optimizing the implementation further.\n\nThe most expensive operations involve converting strings to URI instances. Unfortunately, since using URI is part of the [HttpExchange API](https://docs.oracle.com/en/java/javase/11/docs/api/jdk.httpserver/com/sun/net/httpserver/HttpExchange.html#getRequestURI())  little can be done in this regard. \nIt could be instantiated lazily, but almost all handlers need access to the URI components (e.g. path, query, etc.)\n\nThe standard JDK Headers implementation normalizes all headers to be first character capitalized and the rest lowercase. To ensure optimum performance, client code should use the same format to avoid the normalization cost, i.e. \n\n```java\nUse\n\nvar value = request.getFirst(\"Content-length\");\n\ninstead of\n\nvar value = request.getFirst(\"content-length\"); \nvar value = request.getFirst(\"CONTENT-LENGTH\");\n```\n\n\n","funding_links":[],"categories":["网络编程"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobaho%2Fhttpserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobaho%2Fhttpserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobaho%2Fhttpserver/lists"}