{"id":17091282,"url":"https://github.com/kachayev/quiche4j","last_synced_at":"2025-04-12T22:35:59.483Z","repository":{"id":41993033,"uuid":"293135660","full_name":"kachayev/quiche4j","owner":"kachayev","description":"QUIC transport protocol and HTTP/3 for Java","archived":false,"fork":false,"pushed_at":"2022-08-18T08:54:23.000Z","size":152,"stargazers_count":92,"open_issues_count":10,"forks_count":16,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-26T16:39:12.925Z","etag":null,"topics":["http3","java","network-programming","protocol","quic","rust"],"latest_commit_sha":null,"homepage":"https://kachayev.github.io/quiche4j/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kachayev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-05T18:49:34.000Z","updated_at":"2025-01-22T02:59:37.000Z","dependencies_parsed_at":"2022-08-12T01:40:54.073Z","dependency_job_id":null,"html_url":"https://github.com/kachayev/quiche4j","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kachayev%2Fquiche4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kachayev%2Fquiche4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kachayev%2Fquiche4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kachayev%2Fquiche4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kachayev","download_url":"https://codeload.github.com/kachayev/quiche4j/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248642607,"owners_count":21138352,"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":["http3","java","network-programming","protocol","quic","rust"],"created_at":"2024-10-14T13:58:08.950Z","updated_at":"2025-04-12T22:35:59.464Z","avatar_url":"https://github.com/kachayev.png","language":"Java","funding_links":[],"categories":["网络编程"],"sub_categories":[],"readme":"# Quiche4j\n\nJava implementation of the QUIC transport protocol and HTTP/3.\n\nThe library provides thin Java API layer on top of JNI calls to [quiche](https://github.com/cloudflare/quiche). `Quiche4j` provides a low level API for processing QUIC packets and handling connection state. The application is responsible for providing I/O (e.g. sockets handling) as well as timers. The library itself does not make any assumptions on how I/O layer is organized, making it's pluggle into different architectures.\n\nThe main goal of the JNI bindings is to ensure high-performance and flexibility for the application developers while maintaining full access to `quiche` library features. Specifically, the bindings layer tries to ensure zero-copy data trasfer between runtimes where possible and perform minimum allocations on Java side.\n\n## Usage\n\nMaven:\n\n```xml\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.quiche4j\u003c/groupId\u003e\n        \u003cartifactId\u003equiche4j-core\u003c/artifactId\u003e\n        \u003cversion\u003e0.2.5\u003c/version\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.quiche4j\u003c/groupId\u003e\n        \u003cartifactId\u003equiche4j-jni\u003c/artifactId\u003e\n        \u003cclassifier\u003elinux_x64_86\u003c/classifier\u003e\n        \u003cversion\u003e0.2.5\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nNote that `quiche4j-jni` contains native library and should be installed with proper classifier. [`os-maven-plugin`](https://github.com/trustin/os-maven-plugin) could be used to simplify classifier detection\n\n```xml\n\u003cbuild\u003e\n    \u003cextensions\u003e\n        \u003cextension\u003e\n            \u003cgroupId\u003ekr.motd.maven\u003c/groupId\u003e\n            \u003cartifactId\u003eos-maven-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e1.6.1\u003c/version\u003e\n        \u003c/extension\u003e\n    \u003c/extensions\u003e\n\u003c/build\u003e\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.quiche4j\u003c/groupId\u003e\n        \u003cartifactId\u003equiche4j-jni\u003c/artifactId\u003e\n        \u003cclassifier\u003e${os.detected.classifier}\u003c/classifier\u003e\n        \u003cversion\u003e0.2.5\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\n## Building\n\n`Quiche4j` requires `cargo` and Rust 1.39+ to build. The latest stable Rust release can be installed using [rustup](https://rustup.rs/). Once the Rust build environment is setup,\n\n```bash\n$ git clone https://github.com/kachayev/quiche4j\n$ mvn clean install\n```\n\n## Run Examples\n\nRun HTTP3 client example:\n\n```bash\n$ ./http3-client.sh https://quic.tech:8443\n\u003e sending request to https://quic.tech:8443\n\u003e handshake size: 1200\n\u003e socket.recieve 167 bytes\n\u003e conn.recv 167 bytes\n...\n! conn is closed recv=10 sent=12 lost=0 rtt=95 cwnd=14520 delivery_rate=1436\n```\n\nRun HTTP3 server example:\n\n```bash\n$ ./http3-server.sh :4433\n! listening on localhost:4433\n```\n\n## Compile Manually\n\nMaven project is setup to automatically compile JNI library and include the result of the compilation into the `quiche4j-jni` JAR. Even thought this method is convenient for distribution, it might lack flexibility. To compile JNI manually follow the next steps,\n\n```bash\n$ git clone https://github.com/kachayev/quiche4j\n$ cargo build --release --manifest-path quiche4j-jni/Cargo.toml\n$ mvn clean install\n$ java \\\n    -Djava.library.path=quiche4j-jni/target/release/ \\\n    -cp quiche4j-examples/target/quiche4j-examples-*.jar \\\n    io.quiche4j.examples.Http3Server\n```\n\nThe code would try to load native libraries from `java.library.path` first, using built-in artifact as a fallback only.\n\nFor cross-compilation options, see `cargo build` [documentation](https://doc.rust-lang.org/cargo/commands/cargo-build.html).\n\n## API\n\n### Connection\n\nBefore establishing a QUIC connection, you need to create a configuration object:\n\n```java\nimport io.quiche4j.Config;\nimport io.quiche4j.ConfigBuilder;\n\nfinal Config config = new ConfigBuilder(Quiche.PROTOCOL_VERSION).build();\n```\n\nOn the client-side the `Quiche.connect` utility function can be used to create a new connection, while `Quiche.accept` is for servers:\n\n```java\n// client\nfinal byte[] connId = Quiche.newConnectionId();\n// note, that \"quic.tech\" here is not used for establishing network\n// connection. it's used only for peer verification (thus, optional)\nfinal Connection conn = Quiche.connect(\"quic.tech\", connId, config);\n\n// server\nfinal Connection conn = Quiche.accept(sourceConnId, originalDestinationId, config);\n```\n\n### Incoming packets\n\nUsing the connection's `recv` method the application can process incoming packets that belong to that connection from the network:\n\n```java\nfinal byte[] buf = new byte[1350];\nwhile(true) {\n    DatagramPacket packet = new DatagramPacket(buf, buf.length);\n    try {\n        // read from the socket\n        socket.receive(packet);\n        final byte[] buffer = Arrays.copyOfRange(packet.getData(), packet.getOffset(), packet.getLength());\n        // update the connection state\n        final int read = conn.recv(buffer);\n        if(read \u003c= 0) break;\n    } catch (SocketTimeoutException e) {\n        conn.onTimeout();\n        break;\n    }\n}\n```\n\n### Outgoing packets\n\nOutgoing packet are generated using the connection's `send` method instead:\n\n```java\nfinal byte[] buf = new byte[1350];\nwhile(true) {\n    // get data that's need to be sent based on the connection state\n    final int len = conn.send(buf);\n    if (len \u003c= 0) break;\n    final DatagramPacket packet = new DatagramPacket(buf, len, address, port);\n    // send it to the network\n    socket.send(packet);\n}\n```\n\n### Timers\n\nThe application is responsible for maintaining a timer to react to time-based connection events. When a timer expires, the connection's `onTimeout` method should be called, after which additional packets might need to be sent on the network:\n\n```java\n// handle timer\nconn.onTimeout();\n\n// sending corresponding packets\nfinal byte[] buf = new byte[1350];\nwhile(true) {\n    final int len = conn.send(buf);\n    if (len \u003c= 0) break;\n    final DatagramPacket packet = new DatagramPacket(buf, len, address, port);\n    socket.send(packet);\n}\n```\n\n### Streams Data\n\nAfter some back and forth, the connection will complete its handshake and will be ready for sending or receiving application data.\n\nData can be sent on a stream by using the `streamSend` method:\n\n```java\nif(conn.isEstablished()) {\n    // handshake completed, send some data on stream 0\n    conn.streamSend(0, \"hello\".getBytes(), true);\n}\n```\n\nThe application can check whether there are any readable streams by using the connection's `readable` method, which returns an iterator over all the streams that have outstanding data to read.\n\nThe `streamRecv` method can then be used to retrieve the application data from the readable stream:\n\n```java\nif(conn.isEstablished()) {\n    final byte[] buf = new byte[1350]; \n    for(long streamId: conn.readable()) {\n        // stream \u003cstreamId\u003e is readable, read until there's no more data\n        while(true) {\n            final int len = conn.streamRecv(streamId, buf);\n            if(len \u003c= 0) break;\n        }\n    }\n}\n```\n\n## HTTP/3\n\nThe library provides a high level API for sending and receiving HTTP/3 requests and responses on top of the QUIC transport protocol.\n\n### Connection\n\nHTTP/3 connections require a QUIC transport-layer connection, see [\"Connection\"](#Connection) for a full description of the setup process. To use HTTP/3, the QUIC connection must be configured with a suitable ALPN Protocol ID:\n\n```java\nimport io.quiche4j.Config;\nimport io.quiche4j.ConfigBuilder;\nimport io.quiche4j.http3.Http3Connection;\n\nfinal Config config = new ConfigBuilder(Quiche.PROTOCOL_VERSION)\n    .withApplicationProtos(Http3.APPLICATION_PROTOCOL)\n    .build();\n```\n\nThe QUIC handshake is driven by sending and receiving QUIC packets. Once the handshake has completed, the first step in establishing an HTTP/3 connection is creating its configuration object:\n\n```java\nimport io.quiche4j.http3.Http3Config;\nimport io.quiche4j.http3.Http3ConfigBuilder;\n\nfinal Http3Config h3Config = new Http3ConfigBuilder().build();\n```\n\nHTTP/3 client and server connections are both created using the `Http3Connection.withTransport` function:\n\n```java\nimport io.quiche4j.http3.Http3Connection;\n\nfinal Http3Connection h3Conn = Http3Connection.withTransport(conn, h3Config);\n```\n\n### Sending Request\n\nAn HTTP/3 client can send a request by using the connection's `sendRequest` method to queue request headers; sending QUIC packets causes the requests to get sent to the peer:\n\n```java\nimport io.quiche4j.http3.Http3Header;\n\nList\u003cHttp3Header\u003e req = new ArrayList\u003c\u003e();\nreq.add(new Http3Header(\":method\", \"GET\"));\nreq.add(new Http3Header(\":scheme\", \"https\"));\nreq.add(new Http3Header(\":authority\", \"quic.tech\"));\nreq.add(new Http3Header(\":path\", \"/\"));\nreq.add(new Http3Header(\"user-agent\", \"Quiche4j\"));\nh3Conn.sendRequest(req, true);\n```\n\nAn HTTP/3 client can send a request with additional body data by using the connection's `sendBody` method:\n\n```java\nfinal long streamId = h3Conn.sendRequest(req, false);\nh3Conn.sendBody(streamId, \"Hello there!\".getBytes(), true);\n```\n\n### Handling Responses\n\nAfter receiving QUIC packets, HTTP/3 data is processed using the connection's `poll` method.\n\nAn HTTP/3 server uses `poll` to read requests and responds to them, an HTTP/3 client uses `poll` to read responses. `poll` method accepts object that implements `Http3EventListener` interface defining callbacks for different type of events \n\n```java\nimport io.quiche4j.http3.Http3EventListener;\nimport io.quiche4j.http3.Http3Header;\n\nfinal long streamId = h3Conn.poll(new Http3EventListener() {\n    public void onHeaders(long streamId, List\u003cHttp3Header\u003e headers) {\n        // got headers\n    }\n\n    public void onData(long streamId) {\n        // got body\n        final byte[] body = new byte[MAX_DATAGRAM_SIZE];\n        final int len = h3Conn.recvBody(streamId, body);\n    }\n\n    public void onFinished(long streamId) {\n        // done with this stream\n        conn.close(true, 0x00, \"Bye! :)\".getBytes()));\n    }\n});\n\nif(Quiche.ErrorCode.DONE == streamId) {\n    // this means no event was emitted\n    // it would take more packets to proceed with new events\n}\n```\n\nNote that `poll` would either execute callbacks and returns immediately. If there's not enough data to fire any of the events, `poll` immediately returns `Quiche.ErrorCode.DONE`. The application is responsible for handling incoming packets from the network and feeding packets data into connection before executing next `poll`.\n\n### Examples\n\nHave a look at the [quiche4j-examples](quiche4j-examples/src/main/java/io/quiche4j/examples/) folder for more complete examples on how to use the Quiche4j API to work with HTTP/3 protocol.\n\nExamples package has [`Http3NettyClient`](quiche4j-examples/src/main/java/io/quiche4j/examples/Http3NettyClient.java) with a toy implementation of HTTP/3 client to show case the idea of how `quiche4j` connection state management could be integrated with [Netty](https://netty.io/) I/O primitives.\n\n### Errors Hanlding\n\nNative JNI code propagates errors using return codes (typically the return code \u003c 0 means either DONE or failed). For example, [`quiche::Error`](https://github.com/cloudflare/quiche/blob/204d693bb543e12a605073181ae605eacb743039/src/lib.rs#L320-L365) enum. `Quiche4j` follows the same convention instead of throwing Java exceptions to ensure good perfomance and compatibility with async runtimes (catching exception in async environemnt might be somewhat problematic). See [`Quiche.ErrorCode`](src/main/java/io/quiche4j/Quiche.java) and [`Http3.ErrorCode`](src/main/java/io/quiche4j/http3/Http3.java) for more details.\n\nUnlike other methods, `Quiche.connect` and `Quiche.accept` throw `ConnectionFailureException` if JNI code failed before `quiche::Connection` struct had been allocated. In this case there's no pointer to carry around, thus Java code does not create `Connection` object.\n\n## Debug\n\nUse `QUICHEJ4_JNI_LOG` environment variable to tweak JNI log level. Setting variable to `trace` gives good visibility into the processing. Example\n\n```bash\n$ QUICHE4J_JNI_LOG=trace ./http3-client.sh https://quic.tech:8443\n...\n[2020-09-27T20:49:39Z TRACE quiche] 3457285232348874d2bda1ed5add4a0c894dc9f2 rx pkt Handshake version=ff00001d dcid=3457285232348874d2bda1ed5add4a0c894dc9f2 scid=1b48925e8fcf6281be7f5ca472dd44b71a2f2fc1 len=731 pn=2\n[2020-09-27T20:49:39Z TRACE quiche] 3457285232348874d2bda1ed5add4a0c894dc9f2 rx frm CRYPTO off=2252 len=709\n[2020-09-27T20:49:39Z TRACE quiche::tls] 3457285232348874d2bda1ed5add4a0c894dc9f2 write message lvl=Handshake len=36\n[2020-09-27T20:49:39Z TRACE quiche::tls] 3457285232348874d2bda1ed5add4a0c894dc9f2 set write secret lvl=OneRTT\n[2020-09-27T20:49:39Z TRACE quiche::tls] 3457285232348874d2bda1ed5add4a0c894dc9f2 set read secret lvl=OneRTT\n[2020-09-27T20:49:39Z TRACE quiche] 3457285232348874d2bda1ed5add4a0c894dc9f2 connection established: proto=Ok(\"h3-29\") cipher=Some(AES128_GCM) curve=Some(\"X25519\") sigalg=Some(\"rsa_pss_rsae_sha256\") resumed=false TransportParams { original_destination_connection_id: Some([121, 203, 4, 8, 44, 253, 150, 111, 224, 200, 201, 105, 201, 162, 250, 160]), max_idle_timeout: 30000, stateless_reset_token: None, max_udp_payload_size: 1350, initial_max_data: 10000000, initial_max_stream_data_bidi_local: 1000000, initial_max_stream_data_bidi_remote: 1000000, initial_max_stream_data_uni: 1000000, initial_max_streams_bidi: 100, initial_max_streams_uni: 100, ack_delay_exponent: 3, max_ack_delay: 25, disable_active_migration: true, active_conn_id_limit: 2, initial_source_connection_id: Some([27, 72, 146, 94, 143, 207, 98, 129, 190, 127, 92, 164, 114, 221, 68, 183, 26, 47, 47, 193]), retry_source_connection_id: None }\n...\n```\n\n## Implementation Details\n\n* Modules [Native.java](src/main/java/io/quiche4j/Native.java) and [Http3Native.java](src/main/java/io/quiche4j/http3/Http3Native.java) contains definition of all native calls, structurally close to `quiche`'s [`src/ffi.rs`](https://github.com/cloudflare/quiche/blob/master/src/ffi.rs) and [`src/h3/ffi.rs`](https://github.com/cloudflare/quiche/blob/master/src/h3/ffi.rs) respectively.\n\n* JNI calls are implmeneted in Rust (see [quiche4j-jni](quiche4j-jni/) for more details) using [`rust-jni`](https://docs.rs/jni/0.17.0/jni/) library. The goal was to stick to primitive types as much as possible and avoid Java objects manipulations in native code. There are still a few exceptions from this rule, e.g. operations with connection `Stats`, management of `Http3Header` lists, etc.\n\n* Proxy Java objects maintain a handle (pointer) to the corresponding Rust struct to maximise compatability with all `quiche` features. A single instance of a `Cleaner` is statically defined in `io.quiche4j.Native` class and is used to register all deallocation callback (conventionally called `free` for each class that maintains a native pointer).\n\n## Contribute\n\n* Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug (also, check out \"TODO\" section of this document).\n* Fork the repository on Github \u0026 fork master to `feature-*` branch to start making your changes.\n* Write a test which shows that the bug was fixed or that the feature works as expected.\n\nor simply...\n\n* Use it.\n* Enjoy it.\n* Spread the word.\n\n## TODO\n\nThere are still a few `xxx` comments in the code. Both for Java and for Rust. Plus, there are a few methods that are not exposed to Java layer. Notably, operations with stream priorities and HTTP/3 connection configuration (some of those would require to extend `quiche` library as well).\n\nOther ideas to work on:\n\n- [ ] Propagate Rust panics into Java exceptions (when necessary)\n- [ ] Setup integration testing suite against different QUIC implementations out there\n- [ ] Qlog support\n- [ ] Experiment with in-memory serialization (Arrow?) to deal with (presumably) high overhead of manipulating objects in native code\n\n## Copyright\n\nCopyright (C) 2020, Oleksii Kachaiev.\n\nSee [COPYING](/COPYING) for the license.\n\nSee [cloudflare/quiche/copying](https://github.com/cloudflare/quiche/blob/master/COPYING) for Quiche license.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkachayev%2Fquiche4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkachayev%2Fquiche4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkachayev%2Fquiche4j/lists"}