{"id":16782581,"url":"https://github.com/davidmoten/rxjava2-http","last_synced_at":"2025-03-22T00:31:42.945Z","repository":{"id":32603857,"uuid":"133119599","full_name":"davidmoten/rxjava2-http","owner":"davidmoten","description":"Transmit RxJava2 Flowable over http with non-blocking backpressure","archived":false,"fork":false,"pushed_at":"2024-10-22T20:23:38.000Z","size":446,"stargazers_count":18,"open_issues_count":2,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-23T17:16:20.171Z","etag":null,"topics":["http","io","java","jvm","reactive","reactive-streams","rxjava","rxjava2","stream","streaming-api"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidmoten.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":"2018-05-12T06:15:21.000Z","updated_at":"2024-10-22T20:23:36.000Z","dependencies_parsed_at":"2023-02-10T18:10:23.483Z","dependency_job_id":"31d10d79-76c6-45bb-b119-b567604e42f7","html_url":"https://github.com/davidmoten/rxjava2-http","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Frxjava2-http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Frxjava2-http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Frxjava2-http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Frxjava2-http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidmoten","download_url":"https://codeload.github.com/davidmoten/rxjava2-http/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244169142,"owners_count":20409672,"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":["http","io","java","jvm","reactive","reactive-streams","rxjava","rxjava2","stream","streaming-api"],"created_at":"2024-10-13T07:46:13.062Z","updated_at":"2025-03-22T00:31:42.649Z","avatar_url":"https://github.com/davidmoten.png","language":"Java","readme":"# rxjava2-http\n\u003ca href=\"https://github.com/davidmoten/rxjava2-http/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/davidmoten/rxjava2-http/actions/workflows/ci.yml/badge.svg\"/\u003e\u003c/a\u003e\u003cbr/\u003e\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/rxjava2-http/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/rxjava2-http)\u003cbr/\u003e\n[![codecov](https://codecov.io/gh/davidmoten/rxjava2-http/branch/master/graph/badge.svg)](https://codecov.io/gh/davidmoten/rxjava2-http)\n\nTransmit RxJava2 Flowable over http (with non-blocking backpressure).\n\n*Status*: Released to Maven Central.\n\n## Features\n* Apply non-blocking backpressure to streams over networks\n* Use whatever serialization library you want (the core supports `Flowable\u003cByteBuffer\u003e`)\n* Supports plain HTTP/HTTPS service (no firewall troubles with WebSockets)\n* Uses Servlet 3.1+ asynchronous processing (by default)\n\n\u003cimg src=\"src/docs/marble-diagram.png?raw=true\" /\u003e\n\n## Getting started\n\n### Maven dependency\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.davidmoten\u003c/groupId\u003e\n  \u003cartifactId\u003erxjava2-http\u003c/artifactId\u003e\n  \u003cversion\u003eVERSION_HERE\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Create servlet\nThe servlet below exposes the `Flowable.range(1, 1000)` stream across HTTP using standard java serialization:\n\n```java\n@WebServlet(urlPatterns={\"/stream\"})\npublic class StreamServlet extends FlowableHttpServlet {\n      \n  @Override\n  public Response respond(HttpServletRequest req) {\n    return Response.from(  \n      Flowable\n        .range(1,1000)\n        .map(Serializer.javaIo()::serialize)));\n    }\n}\n```\n\nThe default behaviour is to schedule requests on `Schedulers.io()` and to handle requests asynchronously. These aspects can be configured via other methods in the `Response` builder. See [Throughput](#throughput) section for more details.\n\nBear in mind that each `ByteBuffer` in the server flowable will be reconstituted as is on the client side and you will want to ensure that the client can allocate byte arrays for each item. For example, mobile platforms like Android have quite low max heap sizes so that if the client is one of those platforms you will need to chunk up the data appropriately. (This may be a use-case for supporting [Nested Flowables](#nested-flowables), let me know if you need it).\n\n### Create client\nAssuming the servlet above is listening on `http://localhost:8080/stream`, this is how you access it over HTTP:\n\n```java\nFlowable\u003cInteger\u003e numbers = \n  Client\n   .get(\"http://localhost:8080/stream\") //HTTP GET\n   .deserialized(); // use standard java serialization\n```\nMore client options are available. Here is an example:\n\n```java\nFlowable\u003cInteger\u003e numbers = \n  Client\n    .post(\"https://localhost:8080/stream\") // HTTP POST\n    .connectTimeoutMs(3000) \n    .readTimeoutMs(30000) \n    .proxy(host, port)\n    .sslContext(sslContext)\n    .transform(con -\u003e con.setFollowRedirects(true))\n    .basicAuth(\"username\", \"password\")\n    .deserializer(deserializer) // used for deserialization\n    .rebatchRequests(128); // necessary to enable backpressure over the network without blocking calls\n```\n\nNote that if you need proxy authentication as well then use System properties or set an `Authenticator`:\n\n```java\nAuthenticator authenticator = new Authenticator() {\n    public PasswordAuthentication getPasswordAuthentication() {\n      return (new PasswordAuthentication(\"user\",\n        \"password\".toCharArray()));\n    }\n};\nAuthenticator.setDefault(authenticator);\n```\n\n### SSL/TLS\n\nThe unit test [`ClientSslTest.java`](src/test/java/org/davidmoten/rx2/io/ClientSslTest.java) has a round-trip test using Jetty, TLS 1.2, keystore, truststore and basic authentication. Check it out if you are having trouble.  \n\nHere's an example:\n\n```java\nFlowable\u003cInteger\u003e numbers = \n  Client.get(\"https://localhost:8443\")\n    .sslContext(sslContext)\n    .basicAuth(username, password)\n    .build();\n```\n\n### Serializers\n\n`Serializer.javaIo()` can be used to serialize classes that implement `Serializable`. It is much slower than products like *Kryo* or indeed if you have the time, custom serialization.\n\n## Good practices\n\n### Backpressure\nTo ensure backpressure is applied over the network (so operating system IO buffers don't fill and block threads) it's a good idea to request data in batches (and to request more before the buffer is exhausted to lessen the effect of request overhead):\n\n* apply `rebatchRequests` to the client-side Flowable\n* [*rxjava2-extras*](https://github.com/davidmoten/rxjava2-extras) has a number of request manipulating operators (`minRequest`, `maxRequest` and another version of [`rebatchRequests`](https://github.com/davidmoten/rxjava2-extras#rebatchrequests) with different features)\n\n### Quiet streams\nNote that a long running quiet source Flowable over http(s) is indistinguishable from a chopped connection (by a firewall for instance). To avoid this:\n\n* regularly cancel and reconnect to the stream\n\nOR\n\n* include a heartbeat emission in the Flowable which you filter out on the client side\n\nOR\n\n* put a `timeout` operator on the Flowable on the client side and `retry`\n\nThe heartbeat option is especially good if a reconnect could mean missed emissions.\n\n### Orphan streams on the server\n\nIt's also a good idea to:\n\n*  put a `timeout` operator on the server Flowable in case a client leaves (or is killed) without cancelling\n\nThis goes for any server Flowable, even one that is normally of very short duration. This is because the subscription is retained in a global map until cancellation and will retain some memory. Note that under a lot of GC pressure a container may choose to destroy a servlet (and run `init` again when another call to that servlet happens). In this circumstance `FlowableHttpServlet` is designed to cancel all outstanding subscriptions and release the mentioned map for gc. \n\n### Blocking\n\nThe Flowable returned by the `Client` is blocking in nature (it's reading across a network and can block while doing that). As a consequence make sure you don't run\nit on `Schedulers.computation` (that is one Scheduler we should never block) but rather use `Schedulers.io()` or `Schedulers.from(executor)`.\n\nA quick example of what **NOT** to do is this:\n\n```java\n//run the Client call every 10 seconds\nFlowable\n  .interval(10, TimeUnit.SECONDS) // Not Good Because uses Scheduler.computation()\n  .flatMap(n -\u003e \n      Client\n        .get(\"http://localhost:8080/stream\")\n        .deserialized())\n  .doOnNext(System.out::println)\n  .subscribe(...);\n```        \nInstead you should use an explicit `Scheduler` other than `computation`:\n```\n//run the Client call every 10 seconds on io()\nFlowable\n  .interval(10, TimeUnit.SECONDS, Schedulers.io()) // Good\n  .flatMap(n -\u003e \n      Client\n        .get(\"http://localhost:8080/stream\")\n        .deserialized())\n  .doOnNext(System.out::println)\n  .subscribe(...);\n```\n\n## Design\n* WebSockets is a natural for this but can be blocked by corporate firewalls (and can be problematic with HTTP/2) so this library starts with support for HTTP 1.1. \n* Full duplex HTTP/2 will be great for this application too (later).\n\nWe want API support for these actions:\n\n* *subscribe*\n* *request*\n* *cancel*\n\nSupport is provided via these URL paths\n\nPath | Method | Action | Returns\n--- | --- | --- | --\n`/`   | GET | subscribe with no request | Stream ID then binary stream\n`/?r=REQUEST`|GET | subscribe with initial request | Stream ID then binary stream\n`/?id=ID\u0026r=REQUEST` |GET| request more from given stream | nothing\n`/?id=ID\u0026r=-1` |GET| cancel given stream | nothing\n\nThe format returned in the subscribe calls is (EBNF):\n\n```\nStream ::= Id Item* ( Error | Complete )?\nItem ::= Length Byte*\nError ::= NegativeLength StackTrace \nStackTrace ::= Byte+\nComplete ::= NegativeMinLength\n```\n\n### Stream\n\u003cimg src=\"src/docs/Stream.png?raw=true\"/\u003e\u003cbr/\u003e\n\n### Item\n\u003cimg src=\"src/docs/Item.png?raw=true\"/\u003e\u003cbr/\u003e\n\n### Complete\n\u003cimg src=\"src/docs/Complete.png?raw=true\"/\u003e\u003cbr/\u003e\n\n### Error\n\u003cimg src=\"src/docs/Error.png?raw=true\"/\u003e\u003cbr/\u003e\n\n\nThe core of the library is support for publishing a `Flowable\u003cByteBuffer\u003e` over HTTP(S). Serialization is a little optional extra that occurs at both ends.\n\n### Nested Flowables\nI have a design in mind for publishing nested Flowables over HTTP as well (representing the beginning of a nested Flowable with a special length value). I don't currently have a use case but if you do raise an issue and we'll implement it.\n\n## Throughput\nPeak throughput with embedded jetty server and client on same host, non-SSL, is about 1.3GB/s for 64K byte array items.\n\nThroughput drops considerably for smaller byte arrays (because of overhead per array and frequent flushes):\n\n| ByteBuffer size | Localhost Throughput (MB/s) |\n| ---------------: | -----------------: |\n| 2 | 0.27 |\n| 4 | 0.53 |\n| 8 | 1.2 |\n| 128 | 17 |\n| 512 | 68 |\n| 2K | 245 |\n| 8K | 744 |\n| 32K | 1156 |\n| 64K | 1300 |\n| 128K | 1340 |\n\n### Request patterns and flushing\nBatching requests to balance backpressure and throughput is best tuned with benchmarking. Another aspect you can control is the flushing behaviour of the server. As items are received by the server flowable for publishing across the network to the client flowable each item is by default flushed to the `ServletOutputStream` so that the client gets it immediately instead of waiting for a buffer of bytes to be filled and then sent across. The flushing behaviour can be tuned in the servlet using the `Response` builder methods `autoFlush`, `flushAfterItems` and `flushAfterBytes`. You can specify both items count and byte count threshold at the same time. Here's an example:\n\n```java\n@WebServlet\npublic final class ServletAsync extends FlowableHttpServlet {\n\n    @Override\n    public Response respond(HttpServletRequest req) {\n      return Response \n        .publisher(flowable)\n        .flushAfterItems(10)\n        .flushAfterBytes(8192)\n        .build();\n    }\n}\n\n```\n\nNote that `autoFlush` doesn't do any flushing after `onNext` emissions but relies on the default buffering and flushing behaviour of the servlet. One exception for all the `flush` options that exists to prevent stream stalls under backpressure is that whenever the count of emissions on the server meets the current requested amount a flush is called.\n\n### Server-specific optimizations\n\nWhen a `ByteBuffer` on the server-side is written to the `ServletOutputStream` there are server-specific optimizations that can be made. For instance if the `ByteBuffer` from a memory mapped file and the server is Jetty 9 then the bytes don't need to be copied into the JVM process but can be transferred directly to the network channel by the operating system. Here's an example servlet using such an optimization (using the `writerFactory` builder method in `Response`):\n\n```java\npublic class OptimizedJettyWriterServlet extends FlowableHttpServlet {\n\n    @Override\n    public Response respond(HttpServletRequest req) {\n      return Response //\n        .publisher(flowable) //\n        .writerFactory(OptimizedJettyWriterFactory.INSTANCE) //\n        .build();\n    }\n}\n```\nSee [OptimizedJettyWriterFactory.java](src/test/java/org/davidmoten/rx2/http/OptimizedJettyWriterFactory.java) where you'll notice that the `ServletOutputStream` is cast to a `HttpOutput` which supports writing of `ByteBuffer`s directly.\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Frxjava2-http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidmoten%2Frxjava2-http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Frxjava2-http/lists"}