Lost responses (#1):request() registered its pending future only after sending, so a response that arrived first was dropped and the future never completed. It now registers before sending.
Close handling: pending requests now fail with SessionClosedException when the peer closes or the connection errors. Before, they hung forever.
Concurrent sends:java.net.http.WebSocket allows only one outstanding text send. request, respond and notify called sendText without waiting, so concurrent use could throw IllegalStateException. Sends are now chained one after another.
Split messages:onText ignored the last flag, so a message delivered in several parts was parsed piece by piece and failed. Parts are now buffered until last.
SessionResult.error() built results with isError = false, so errors from request handlers were sent as response instead of errorresponse.
close() called sendClose(0, ...). 0 isn't a valid close code, so it threw IllegalArgumentException and never closed. It now sends NORMAL_CLOSURE (1000).
Unknown methods: requests for an unknown method now get an errorresponse, matching session-rs 0.2, instead of no reply. A handler that throws also produces an error response.
API additions
These are source-compatible, except that the send methods now return futures.
Session.onClose(Runnable): runs exactly once, whether the session closed locally, by the peer, or on a network error. It runs immediately if the session is already closed.
Session.isOpen().
Session.request(method, req, Duration timeout): completes with a TimeoutException when the timeout passes.
SessionErrorException (with getError() returning the error payload) and SessionClosedException, both RuntimeExceptions. Before, errors completed with a plain RuntimeException(error.toString()).
send, respond, respondError and notify return CompletableFuture<Void> instead of void throws Exception.
Build and distribution
JitPack can't build this repo any more, because the GitHub repo is gone. Publishing moves to the Gitea package registry:
coordinates: dev.selimaj:session-java:0.2.0
publish with GITEA_TOKEN=<package:write token> ./gradlew publish
Jackson becomes an api dependency, since JsonNode is part of the public API.
The unused jakarta.websocket-api and tyrus-client dependencies are removed.
Sources jar added. The version was stuck at 0.1.3, and is now 0.2.0.
README: installation, usage, semantics and publishing; the protocol section now documents errorresponse and the real notification shape.
Testing
The empty SessionTest is replaced by 9 tests against an in-process Java-WebSocket server:
request/response
error responses
800 concurrent requests from 8 threads against a server that answers immediately, covering both the race and the concurrent sends
pending requests failing and onClose running once when the server closes
request timeout
a response split into 3 frames
handler results, error results and unknown methods being answered
notifications
local close() sending 1000
All pass, 6 of 6 runs.
The Saturn client (1.21.11) was also run against this build, installed with publishToMavenLocal. It authenticated, noticed when the server was stopped, retried with backoff, and reconnected when it came back.
After merging
Publish: GITEA_TOKEN=... ./gradlew publish.
Check https://git.selimaj.dev/api/packages/selimaj-dev/maven/dev/selimaj/session-java/0.2.0/session-java-0.2.0.pom downloads without auth.
The Saturn client PRs (reconnect and timeouts) depend on it.
Fixes #1.
## Summary
### Fixes
- **Lost responses (#1):** `request()` registered its pending future only *after* sending, so a response that arrived first was dropped and the future never completed. It now registers before sending.
- **Close handling:** pending requests now fail with `SessionClosedException` when the peer closes or the connection errors. Before, they hung forever.
- **Concurrent sends:** `java.net.http.WebSocket` allows only one outstanding text send. `request`, `respond` and `notify` called `sendText` without waiting, so concurrent use could throw `IllegalStateException`. Sends are now chained one after another.
- **Split messages:** `onText` ignored the `last` flag, so a message delivered in several parts was parsed piece by piece and failed. Parts are now buffered until `last`.
- **`SessionResult.error()`** built results with `isError = false`, so errors from request handlers were sent as `response` instead of `errorresponse`.
- **`close()`** called `sendClose(0, ...)`. 0 isn't a valid close code, so it threw `IllegalArgumentException` and never closed. It now sends `NORMAL_CLOSURE` (1000).
- **Unknown methods:** requests for an unknown method now get an `errorresponse`, matching session-rs 0.2, instead of no reply. A handler that throws also produces an error response.
### API additions
These are source-compatible, except that the send methods now return futures.
- `Session.onClose(Runnable)`: runs exactly once, whether the session closed locally, by the peer, or on a network error. It runs immediately if the session is already closed.
- `Session.isOpen()`.
- `Session.request(method, req, Duration timeout)`: completes with a `TimeoutException` when the timeout passes.
- `SessionErrorException` (with `getError()` returning the error payload) and `SessionClosedException`, both `RuntimeException`s. Before, errors completed with a plain `RuntimeException(error.toString())`.
- `send`, `respond`, `respondError` and `notify` return `CompletableFuture<Void>` instead of `void throws Exception`.
### Build and distribution
- JitPack can't build this repo any more, because the GitHub repo is gone. **Publishing moves to the Gitea package registry**:
- coordinates: `dev.selimaj:session-java:0.2.0`
- publish with `GITEA_TOKEN=<package:write token> ./gradlew publish`
- Jackson becomes an `api` dependency, since `JsonNode` is part of the public API.
- The unused `jakarta.websocket-api` and `tyrus-client` dependencies are removed.
- Sources jar added. The version was stuck at `0.1.3`, and is now `0.2.0`.
- README: installation, usage, semantics and publishing; the protocol section now documents `errorresponse` and the real notification shape.
## Testing
The empty `SessionTest` is replaced by **9 tests** against an in-process Java-WebSocket server:
- request/response
- error responses
- 800 concurrent requests from 8 threads against a server that answers immediately, covering both the race and the concurrent sends
- pending requests failing and `onClose` running once when the server closes
- request timeout
- a response split into 3 frames
- handler results, error results and unknown methods being answered
- notifications
- local `close()` sending 1000
All pass, 6 of 6 runs.
The Saturn client (1.21.11) was also run against this build, installed with `publishToMavenLocal`. It authenticated, noticed when the server was stopped, retried with backoff, and reconnected when it came back.
## After merging
1. Publish: `GITEA_TOKEN=... ./gradlew publish`.
2. Check `https://git.selimaj.dev/api/packages/selimaj-dev/maven/dev/selimaj/session-java/0.2.0/session-java-0.2.0.pom` downloads without auth.
3. The Saturn client PRs (reconnect and timeouts) depend on it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Register pending requests before sending so fast responses aren't lost
- Fail pending requests with SessionClosedException when the connection
closes or errors, and add onClose(), isOpen() and a request overload
with a timeout
- Serialize text sends: java.net.http.WebSocket allows only one
outstanding send, so concurrent requests could throw
- Reassemble text messages delivered in parts before parsing
- Complete error responses with SessionErrorException carrying the payload
- Answer requests for unknown methods with an error response
- Fix SessionResult.error() producing success responses
- Fix close() using invalid close code 0; send 1000
- Publish as dev.selimaj:session-java to the Gitea package registry;
make Jackson an api dependency and drop unused Tyrus/Jakarta deps
- Replace the empty test with 9 tests against an in-process server
Fixes#1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #1.
Summary
Fixes
request()registered its pending future only after sending, so a response that arrived first was dropped and the future never completed. It now registers before sending.SessionClosedExceptionwhen the peer closes or the connection errors. Before, they hung forever.java.net.http.WebSocketallows only one outstanding text send.request,respondandnotifycalledsendTextwithout waiting, so concurrent use could throwIllegalStateException. Sends are now chained one after another.onTextignored thelastflag, so a message delivered in several parts was parsed piece by piece and failed. Parts are now buffered untillast.SessionResult.error()built results withisError = false, so errors from request handlers were sent asresponseinstead oferrorresponse.close()calledsendClose(0, ...). 0 isn't a valid close code, so it threwIllegalArgumentExceptionand never closed. It now sendsNORMAL_CLOSURE(1000).errorresponse, matching session-rs 0.2, instead of no reply. A handler that throws also produces an error response.API additions
These are source-compatible, except that the send methods now return futures.
Session.onClose(Runnable): runs exactly once, whether the session closed locally, by the peer, or on a network error. It runs immediately if the session is already closed.Session.isOpen().Session.request(method, req, Duration timeout): completes with aTimeoutExceptionwhen the timeout passes.SessionErrorException(withgetError()returning the error payload) andSessionClosedException, bothRuntimeExceptions. Before, errors completed with a plainRuntimeException(error.toString()).send,respond,respondErrorandnotifyreturnCompletableFuture<Void>instead ofvoid throws Exception.Build and distribution
dev.selimaj:session-java:0.2.0GITEA_TOKEN=<package:write token> ./gradlew publishapidependency, sinceJsonNodeis part of the public API.jakarta.websocket-apiandtyrus-clientdependencies are removed.0.1.3, and is now0.2.0.errorresponseand the real notification shape.Testing
The empty
SessionTestis replaced by 9 tests against an in-process Java-WebSocket server:onCloserunning once when the server closesclose()sending 1000All pass, 6 of 6 runs.
The Saturn client (1.21.11) was also run against this build, installed with
publishToMavenLocal. It authenticated, noticed when the server was stopped, retried with backoff, and reconnected when it came back.After merging
GITEA_TOKEN=... ./gradlew publish.https://git.selimaj.dev/api/packages/selimaj-dev/maven/dev/selimaj/session-java/0.2.0/session-java-0.2.0.pomdownloads without auth.🤖 Generated with Claude Code