{"id":18038837,"url":"https://github.com/timboudreau/blather","last_synced_at":"2025-03-27T10:31:42.540Z","repository":{"id":38306489,"uuid":"105506597","full_name":"timboudreau/blather","owner":"timboudreau","description":"Async Java websocket client and test harness","archived":false,"fork":false,"pushed_at":"2023-05-15T03:59:24.000Z","size":82,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T15:51:19.572Z","etag":null,"topics":["async","java","netty","websocket","websocket-client"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/timboudreau.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-10-02T07:04:39.000Z","updated_at":"2023-05-23T02:09:55.000Z","dependencies_parsed_at":"2024-10-30T14:06:44.117Z","dependency_job_id":"3aa62ca6-7ca7-4003-88b4-704317f8a56b","html_url":"https://github.com/timboudreau/blather","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Fblather","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Fblather/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Fblather/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Fblather/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timboudreau","download_url":"https://codeload.github.com/timboudreau/blather/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245826936,"owners_count":20678883,"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":["async","java","netty","websocket","websocket-client"],"created_at":"2024-10-30T14:06:34.147Z","updated_at":"2025-03-27T10:31:42.236Z","avatar_url":"https://github.com/timboudreau.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Blather - A Netty Based Websocket Client and Test Harness\n=========================================================\n\nBlather is a Netty-based Java websocket client, with a test harness that makes\nit trivial to write JUnit tests for websocket communication.\n\nIt emphasizes simplicity and functional interfaces. Code being worth a thousand\nwords, here is a trivial client that just connects, sends \"hello\" and then\n\"goodbye \" with whatever response it got appended:\n\n```java\npublic class WebsocketTest {\n    public static void main(String[] args) {\n        Blather.create().client(\"ws://foo.example/websocket\").sendOnConnect(\"hello\")\n                .onMessage(String.class, WebsocketTest::onMessage);\n    }\n    public static Object onMessage(int msgIndex, String inboundMessage, ChannelControl ctrl) {\n        return \"goodbye \" + inboundMessage;\n    }\n}\n```\n\nNote the use of JDK 8 member references to separate the logic of connecting from the\ncode that has the conversation.\n\nTo use, add the Maven repository as [described here](https://timboudreau.com/builds/) and\n\n```xml\n\u003cdependency\u003e\n    \u003cartifactId\u003eblather\u003c/artifactId\u003e\n    \u003cgroupId\u003ecom.mastfrog\u003c/groupId\u003e\n    \u003cversion\u003e2.0.1-dev\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nManipulating The Connection\n---------------------------\n\nThe `ChannelControl` interface passed into all callbacks allows you to:\n\n * Asynchronously send messages to the server, rather than sending them as the return\nvalue\n * Close the connection (politely, sending a close frame)\n * Replace the current handler with another one - so if your connection has various\nmodes - perhaps its own handshaking phase followed by other communication - you can\nsimply hand off message handling to a different handler by calling `ChannelControl.nextCallback()`.\n\nData Marshalling\n----------------\n\nBlather uses [Jackson](https://github.com/FasterXML/jackson) for data marshalling for non-string\ntypes (note that Jackson can speak more than JSON - for example, [Bson4Jackson](https://github.com/michel-kraemer/bson4jackson)).\nSo, you'll note that the message handler method above simply returned `Object` - you have\nflexibility here - you can return\n\n * A `String` or any `CharSequence`, which will be marshalled to a plain text websocket frame\n * A Netty `WebSocketFrame`, which will be sent as-is\n * Any other object, which will be marshalled using Jackson (you can supply and configure the\n`ObjectMapper` using oen of the other factory methods on `Blather`.\n * Directly get the Netty `Channel` object to do as you wish with\n\nException Handling\n------------------\n\nOne of the main use cases is writing unit- or functional-tests, so out of the box exception\nhandling is dealt with in a particular way:\n\n * In a test, simply call `WebsocketClientRequest.await()`, and any exception thrown will cause\nthe connection to be closed, and the exception will be rethrown in the main thread (any\nsubsequent exceptions will show up as suppressed exceptions in the stack trace of the first)\n\n * For other environments (where you don't have a thread blocked waiting for request completion - \nthis is an asynchronous library after all), provide a `WebsocketErrorHandler` to \n`WebsocketClientRequest.withErrorHandler()` and that can decide whether to close the\nconnection or not.\n\nTest Harness\n============\n\nThere is a `test-jar` Maven artifact which includes a test harness for writing tests which\nautomatically start a server.  While designed with [Acteur](https://github.com/timboudreau/acteur) in\nmind, the `Server` and `ServerControl` interfaces are \n[trivial to implement](https://timboudreau.com/builds/job/mastfrog-parent/lastSuccessfulBuild/artifact/acteur-modules/acteur-parent/acteur-util/target/apidocs/com/mastfrog/acteur/util/Server.html).\n\nHere is a test which starts a small Acteur server, makes a websocket connection, does some\nstuff and closes it:\n\n```java\n@RunWith(GuiceRunner.class)\n@TestWith(TestApplication.Module.class)\npublic class HarnessTest {\n    int count = 0;\n    @Test(timeout=20000)\n    public void test(WebsocketHostClient client) throws Throwable {\n        client.request(\"/ws\")\n                .log()\n                .addHeader(Headers.stringHeader(\"X-Foo\"), \"bar\")\n                .addUrlQueryPair(\"foo\", \"bar\")\n                .onMessage(String.class, this::withFrame)\n                .await(Duration.ofSeconds(20));\n    }\n\n    public Object withFrame(int msgIndex, String data, ChannelControl ctrl) {\n        if (count++ == 4) {\n            ctrl.close();\n            fail(\"Handler should have been replaced\");\n            return null;\n        } else if (count == 3) {\n            ctrl.nextCallback(this::bypass, String.class);\n        }\n        return \"Hello: \" + data;\n    }\n\n    public Object bypass(int msgIndex, String data, ChannelControl ctrl) {\n        System.out.println(\"BYPASS GOT \" + data);\n        ctrl.close();\n        return null;\n    }\n}\n```\n\nThe test harness also has the ability, with Acteur (or anything that wants to\ninject an `ErrorInterceptor` and call it on errors) to catch server-side exceptions\nand rethrow those at the end of a test, so that server side errors are not opaque\nto the test, and they simply show up as test failures with a stack trace.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimboudreau%2Fblather","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimboudreau%2Fblather","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimboudreau%2Fblather/lists"}