Re: HttpClient API usage feedback/questions
On Wed, 6 Jun 2018 at 21:03, Chris Hegarty wrote: > > > On 4 Jun 2018, at 03:48, James Roper wrote: > > > > ... > > > > +1 on variants of getContentType that give you either the media type or > the charset parsed from the content type header. Without them, we're going > to end up with a lot of regexps doing ad hoc parsing to get the information > needed out of the Content-Type header. For example, if you want to know if > the incoming request is application/json, text/plain, or application/xml, > in order to parse that correctly, you'll need to do things like > .matches("text/plain(?;.*)?"). Whereas, if you had a getMediaType() you > could just do a .equals("text/plain"). > > I agree that it can be a little cumbersome, but still possible, to get > to the meat of specific header values like Content-Type. There is a > mini-design space around this; should a single method be exposed to > return the Charset, or a higher-level interface like ContentType that > exposes the media-type and the list of parameters, exceptions/defaults, > etc? > Completely agreed that this is an important discussion to have. Personally I don't have a strong opinion, I've had experience maintaining both styles of API and for me there's no clear winner in which approach is best. Here's a few additional considerations: * Scope is always an issue with these types of features. Why stop at just content type? Why not also add high level modelling around accept headers? And so on... I think we need to carefully decide what the criteria is for making a header a first class concept in the API. Some questions to ask there (which I'm guessing you already have a good understanding of, but I don't) is how much we expect this API to be used directly by end users, and how much we expect it to be used underneath a higher level API, either as a third party open source library, or perhaps we expect users to create their own use case specific abstractions on top? The lower level it will sit (on average), the less demand for higher level concepts in the API. * If we model it using a higher level interface like ContentType, and maybe we decide to model a few other headers using a higher level interface too, then why not model all headers using a higher level interface, and use some sort of strongly typed map API to get the header values? For example: interface HeaderKey { String getHeaderName(); V parse(String value); } class ContentType { ... } class ContentTypeKey implements HeaderKey { String getHeaderName() { return "Content-Type"; } ContentType parse(String value) { // ... } } class HttpHeaders { V getHeader(HeaderKey key) { String value = getHeader(key.getHeaderName()); if (value != null) { return key.parse(value); } else { return null; } } ... } The above uses a parse on the fly approach (which would result in multiple parses on each access), other approaches might involve registering header keys when creating the client (and this can be taken advantage of to do caching of parsed values, particularly with an HTTP2 underlying impl) etc. Anyway, the above obviously means introducing a lot more API, and wouldn't be worth it if the only thing we're interested in adding a convenience for is the content type, but may be more useful if we want to add convenience methods for more headers, since it means other libraries can provide their own convenience methods using the same mechanism for parsing and accessing headers. I filed the following enhancement request to track this: > https://bugs.openjdk.java.net/browse/JDK-8204470 > > Since this request is more of a convenience API, and is additive, it may > not happen in time for 11. There are a number of other higher priority > issues that need to be resolved before JDK 11 RDP 1 [1] . Some of these > have minor impact on the existing API, and as such need to be resolved > in 11. > > -Chris. > > [1] http://openjdk.java.net/projects/jdk/11/ > > > > -- *James Roper* *Senior Developer, Office of the CTO* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: HttpClient API usage feedback/questions
On Sat, 2 Jun 2018 at 16:52, Jaikiran Pai wrote: > 2. Would it be a good idea to add APIs (perhaps in HttpHeaders class) to > return values of higher level constructs represented by the headers. > Imagine certain well-known headers in the response, like Content-Length > and Content-Type. Would it be a good idea to expose higher level APIs of > the form getContentLength(), getContentCharset() and such? It probably > doesn't add much value to Content-Length header but there's a particular > mechanism the charset is supposed to be parsed out of the "Content-Type" > header and having it done with the API's implementation would prevent > all the parsing/logic in every other application. > -1 on getContentLength, it's a detail of handling the message, it often won't be there (eg chunked encoding in HTTP 1.1 or regular content length less in HTTP 2), and so it should only be relevant to the BodyHandler (or whatever it's called now, I forget which APIs have been renamed to what). I don't think that's an important enough use case, nor do I think it adds enough value, to warrant support in the API. +1 on variants of getContentType that give you either the media type or the charset parsed from the content type header. Without them, we're going to end up with a lot of regexps doing ad hoc parsing to get the information needed out of the Content-Type header. For example, if you want to know if the incoming request is application/json, text/plain, or application/xml, in order to parse that correctly, you'll need to do things like .matches("text/plain(?;.*)?"). Whereas, if you had a getMediaType() you could just do a .equals("text/plain"). -- *James Roper* *Senior Developer, Office of the CTO* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: OT? reactive streams
Hi Simon, This is the right place to talk about the new HTTP client. I wrote a blog post which has some hypothetical examples of integrating the HTTP client with other APIs (such as the servlet API) and their hypothetical future adoption of Reactive Streams: https://developer.lightbend.com/blog/2018-02-06-reactive-streams-ee4j/index.html Not sure if that will be useful to you or not. You're right that there are no pre built processors for doing map/flatmap/filter. This is something that I (and my company, Lightbend) am looking to address, independent of the HTTP client. The current way to do these transformations is using a third party Reactive Streams library, such as Akka Streams, RxJava or Reactor. In MicroProfile, we are creating a generic library for doing these transformations, and after some incubation in there, we are hoping that the JDK maintainers will pull this library in the JDK (we've seen some support from JDK maintainers for this path in private channels). Here are some resources relevant to this effort (everything is under active development, so things are moving fairly quickly at the moment): https://github.com/eclipse/microprofile-reactive/pull/3/files https://github.com/eclipse/microprofile-reactive/tree/master/streams http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-March/051799.html Cheers, James On Thu, 17 May 2018 at 07:32, Simon Roberts <si...@dancingcloudservices.com> wrote: > Hi, apologies for the rather off topic inquiry, but I'm trying to find out > more about the reactive streams features that are used inside the new > httpClient. I tried to subscribe to what I think is the appropriate list > but appeared to hit a "bug in mailman" (three times, so not, apparently, > transient). > > I'm hoping to discover if there are samples, examples, whatever for the > streams features (I was successful in implementing a crude BodyHandler / > BodySubscriber, so the basics make sense. > > In particular, this feels like there might be Processors that perform > map/flatmap/filter operations, prebuilt for ease of use > > Also, it feels like there might be examples or tools imteracting with GUI > comonents, or perhaps performing infinite series math, like sieve of > Erastothanes (sp!?) or the Pi approximation series. > > Can anyone tell me if they know of any such resources for > learning/tinkering? > > Thanks! > > -- > Simon Roberts > (303) 249 3613 > > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: java.net.Socket should report the attempted address and port
This would be especially useful in asynchronous applications - since in those cases the exception rarely maps back to a place in user code that would indicate what is being connected to. As someone who has spent a lot of time supporting developers who use asynchronous libraries and post exceptions of this nature (supporting both in open source, eg on stack overflow, as well as providing commercial support), where I don't have access to their code base so I can't do the necessary investigations directly myself, having the attempted address and port in the error message would save a lot of time, and probably even prevent a lot of people from requiring support in the first place. On 22 April 2018 at 20:59, Péter Gergely Horváth < peter.gergely.horv...@gmail.com> wrote: > Hi All, > > I am wondering if it would be possible to make a minor improvement to the > way *java.net.Socket* reports connectivity errors and incorporate the > attempted address, port and the timeout used into the exception message. > > The current implementation emits a generic error message, which is not > that helpful when one is operating a _large_ application. (Consider e.g. > Big Data or complex legacy systems written by someone else). > > java.net.ConnectException: Connection refused (Connection refused) > at java.net.PlainSocketImpl.socketConnect(Native Method) > at java.net.AbstractPlainSocketImpl.doConnect( > AbstractPlainSocketImpl.java:350) > at java.net.AbstractPlainSocketImpl.connectToAddress( > AbstractPlainSocketImpl.java:206) > at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java: > 188) > at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) > at java.net.Socket.connect(Socket.java:589) > at java.net.Socket.connect(Socket.java:538) > at java.net.Socket.(Socket.java:434) > at java.net.Socket.(Socket.java:211) > at Sample.main(Sample.java:9) > > > I have looked into the JDK code base and implemented a patch that reports > the address, port and timeout used in the error message without touching > any native parts (see attached patch file). I have tested this (created my > own build of the JDK and run a sample application against it) and it seems > to work properly. > > Would it be possible to incorporate this change into the official JDK code > base? I do believe it would help a lot of people out there. > > Based on my understanding, once I have signed the OCA, I should simply > write an email to the group and request a sponsor to pick up this issue. > Could someone help me with this? > > Thank you, > Peter > > > > > > > > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: RFR [11] 8197564: HTTP Client implementation - JEP 321
parameter, which would be better for future maintenance in case other > information needs to be provided. > The simple case that comes to mind is the HTTP version of the > response, which may be different from that of the request and > indicates server capabilities: this is currently not provided in the > BodyHandler.apply() signature. > > So perhaps: > > public Publisher apply(HttpResponse response, Publisher > responseContent); > > This change would also simplify the maintenance since BodySubscriber > will be removed. > > My concern is that driving users of the JDK APIs outside of the > familiar RS APIs offered by Reactor and RxJava2 where only Publishers > are relevant, by forcing them to implement Subscribers and Processors > (heck, I even have an IntelliJ warning telling me that I should not do > that!), will cause confusion and be potential source of bugs - we all > know non-blocking/async programming is hard. > > Thanks ! > > -- > Simone Bordet > --- > Finally, no matter how good the architecture and design are, > to deliver bug-free software with optimal performance and reliability, > the implementation technique must be flawless. Victoria Livschitz > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: RFR [11] 8197564: HTTP Client implementation - JEP 321
the rate produced by the remote end, potentially buffering and running out of memory, or having to fail. So, as you can see, WebSockets don't need an explicit backpressure mechanism in the protocol, they can piggy back off TCP. And Reactive Streams can use this to ensure a consumer of a WebSocket stream on one end can push back on the producer at the other end, and have that push back propagated all the way through, albeit with a certain amount of buffering delaying the push back from happening immediately. > Thanks. > > >> > with the understanding that such a helper will limit the capabilities >>>> and performance when its used - eg, the range of options for closing, range >>>> of options for how errors are handled, and perhaps introduce some small >>>> performance penalties such as additional allocations and/or buffer copies. >>>> >>>> That is a whole set of comprises and decisions that would need to >>>> be considered. >>>> >>>> > The use case here is how well this API will interact with other APIs. >>>> WebSockets are rarely used in isolation from other technologies. Most non >>>> trivial usages of WebSockets will involve plumbing the WebSocket API to >>>> some other source or sink of streaming data - for example, a message >>>> broker, a database, perhaps a gRPC stream to another service. >>>> >>>> I could well image a set of vertical-market specific libraries being >>>> built >>>> on top of the WebSocket API, and that is how it should be. In this >>>> specific area, providing the lowest level API is an enabler for others. >>>> >>>> > The question is, how difficult will it be to connect those two APIs? >>>> >>>> I imagine some work may be required, but certainly much less than >>>> without the WebSocket API. >>>> >>>> > Will their backpressure mechanisms map to each other easily? >>>> >>>> I see no reason that it should not. Are you aware of any? >>>> >>>> > Will their failure handling and error handling semantics map to each >>>> other easily? >>>> >>>> I see no reason that they should not. In fact, most higher-level APIs >>>> provide more coarse grain error handling. >>>> >>>> > How many lines of boilerplate will it take to hook them up? >>>> >>>> I guess that depends on the particular library's functionality, will it >>>> support/handle/expose partial message delivery, buffering, etc. I >>>> don’t see this as boilerplate though. >>>> >>>> > If the WebSocket provides an option to use Reactive Streams, then for >>>> any other sources/sink of streamed data that support Reactive Streams, >>>> application developers will have an option where the answers are guaranteed >>>> to be yes without the developer having to do any mapping themselves, and >>>> the lines of code to do that will be one. >>>> >>>> Sure, if one has a higher-level library that works with Reactive >>>> Streams, >>>> and one wants to use WebSocket as a transport, then it would be little >>>> work if Java SE provided a Reactive Streams interface to WebSocket. >>>> There are also vertical-markets using WebSocket, but not using Reactive >>>> Streams. >>>> >>>> At this point in JEP 321, I do not want to increase the scope of the >>>> project >>>> to include a Reactive Streams interface for WebSocket. This is something >>>> that can, and should, be considered separate to JEP 321. It could follow >>>> in a future release if deemed appropriate, or not, but adding it at >>>> this point >>>> will add risk to JEP 321, which I am not willing to do. >>>> >>>> -Chris. >>>> >>>> >>> >> >> >> -- >> *James Roper* >> *Senior Octonaut* >> >> Lightbend <https://www.lightbend.com/> – Build reactive apps! >> Twitter: @jroper <https://twitter.com/jroper> >> > > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: RFR [11] 8197564: HTTP Client implementation - JEP 321
Hi Chris, I'm still really keen to see an option provided out of the box for using Reactive Streams with the WebSocket API. Note that I'm not proposing that anything be changed about the existing API, I understand fully that you want to provide a full powered API that offers the full range of WebSocket features with very high performance, and mapping that to the Reactive Streams API goes against that. Rather, I think that there should be a built in helper that allows Reactive Streams to be added on top, with the understanding that such a helper will limit the capabilities and performance when its used - eg, the range of options for closing, range of options for how errors are handled, and perhaps introduce some small performance penalties such as additional allocations and/or buffer copies. The use case here is how well this API will interact with other APIs. WebSockets are rarely used in isolation from other technologies. Most non trivial usages of WebSockets will involve plumbing the WebSocket API to some other source or sink of streaming data - for example, a message broker, a database, perhaps a gRPC stream to another service. The question is, how difficult will it be to connect those two APIs? Will their backpressure mechanisms map to each other easily? Will their failure handling and error handling semantics map to each other easily? How many lines of boilerplate will it take to hook them up? If the WebSocket provides an option to use Reactive Streams, then for any other sources/sink of streamed data that support Reactive Streams, application developers will have an option where the answers are guaranteed to be yes without the developer having to do any mapping themselves, and the lines of code to do that will be one. Regards, James On 22 March 2018 at 07:33, Chris Hegarty <chris.hega...@oracle.com> wrote: > Hi, > > This is a request for review for the HTTP Client implementation - JEP 321. > > The javadoc link has been refreshed, based on feedback received from > the CSR [1] ( and a follow on supplementary CSR [2] ): > http://cr.openjdk.java.net/~chegar/httpclient/02/javadoc/ > api/java.net.http/java/net/http/package-summary.html > > The webrev link has been refreshed to include implementation changes > to support the latest API, additional test code coverage and scenarios: > http://cr.openjdk.java.net/~chegar/httpclient/02/webrev/ > > > On 9 Feb 2018, at 14:33, Chris Hegarty <chris.hega...@oracle.com> wrote: > > Development of the JDK HTTP Client, incubated in JDK 9 and re-incubated > > in JDK 10, has continued. It will soon be in a position to be proposed > > for inclusion in Java 11, through JEP 321. This will proceed, as usual, > > through the JEP 2.0 process. > > > > JEP 321 has recently been updated with the proposed package and module > > name, `java.net.http`. The code in the sandbox has been updated to > > reflect this. > > > > Javadoc based on the latest code in the sandbox: > > http://cr.openjdk.java.net/~chegar/httpclient/00/javadoc/ > api/java.net.http-summary.html > > > > Webrev based on the latest code in the sandbox: > > http://cr.openjdk.java.net/~chegar/httpclient/00/webrev/ > > > > ( much of the changes in the webrev are mechanical due to renaming, but > > there are stability fixes and the API documentation has been > > reorganized to improve readability ) > > > > More information about the JDK HTTP Client can be found on the > > networking groups page. > > http://openjdk.java.net/groups/net/httpclient/ > > > -Chris. > > [1] https://bugs.openjdk.java.net/browse/JDK-8197565 > [2] https://bugs.openjdk.java.net/browse/JDK-8199938 -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: RFE: Add PATCH to the list of the supported HTTP methods
I don't have any opinion on the matter, but a datapoint to the popularity of PATCH is its use in the GitHub API: https://developer.github.com/v3/ GitHub is often looked to as a gold standard for how to write a REST API, so if they do it, it's likely that many others do too. On 7 March 2018 at 05:40, Chris Hegarty <chris.hega...@oracle.com> wrote: > Hi Andrej, > > On 02/03/18 08:28, Andrej Golovnin wrote: > >> Hi all, >> >> it would be nice if could add PATCH >> (https://tools.ietf.org/html/rfc5789) to the list of the supported >> ... >> - and one for the HTTP Client branch in the JDK sandbox repository >> which adds PATCH to the HttpRequest builder: >>java_net_http_HttpRequest_PATCH_method.diff >> > > The JDK HTTP Client has: > `HttpRequest.Builder::method(String method, BodyPublisher publisher)` > > ,so it is currently possible to use the `PATCH` method. > > Is `PATCH` sufficiently popular to warrant its own self-named > method in the request builder? > > -Chris. > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: websockets
Hi Chuck, I'm definitely not saying that the Java client is for browsers. I'm saying that the WebSocket protocol was designed to be used by the API provided by web browsers. No protocol is ever implemented on an island, it is implemented with an intended use case. To best understand a protocol, to understand why it is the way it is, to understand what would be a good way to use it, and what might be a bad way to use it, and therefore to understand what an API for it should look like, it is good to understand what use case the protocol was designed for. And that use case was to be used from browsers. So when you create a WebSocket client, or server, in any language, it is very valuable to look at the browser provided APIs to understand how the protocol was intended to be used, and therefore what features you should expose to users. To put in perspective, most server implementations of WebSockets are designed to be connected to from browsers, non browser clients are still a relatively niche use case for WebSockets. And so servers are going to look to the browser for what features that should give priority to. If a browser doesn't support X, then it's likely that a server implemention either won't support X, or won't provide very good or easy to use support for X. Consequently, it's of dubious value to provide a new client where support for X complicates the API, since servers aren't going to support that well anyway, it's providing a feature that you won't be able to use at the cost of making other features harder to use. Regards, James On 4 March 2018 at 05:49, Chuck Davis <cjgun...@gmail.com> wrote: > James, this does not make any sense to me. Last I heard browsers no > longer run java. Why would JSE give a hang about designing a > communications mechanism for browsers on the client side? That is the job > of the browser developers. On the other hand, JSE WebSocket is the best > thing to come along for java clients since RMI which was released I think > in 1.1. Java WebSocket is NOT for browsersit's for Java clients > And I'm quite pleased with the progress I've made refactoring to use > the jdk9 WebSocket api together with wildfly11 on the server (and still > blazingly FAST -- seems to be snappier than jdk8 implementation). Now if > the IDEs would get in gear to fully implement jdk9 I'd be in fat city. > > And my thanks to everyone who has participated in this particular > conversation on this list and all the expertise that has been shared. > > >Yes, there's no formal definition of high/low level, but in the case of > WebSockets, we can draw some lines based roughly on what web browsers > (which is the target application developer experience that >WebSockets were > designed for) support as being high level, and what the WebSocket protocol > allows beyond this as low level. > >> >> Regards, >> >> James >> >> > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: websockets
Hi Simone, Sorry, I think there's been some missed information here. I am in no way suggesting that the API as provided currently should be changed or replaced. What I'm suggesting is that a high level adapter be provided out of the box that provides Reactive Streams support on top of this API. Yes, there's no formal definition of high/low level, but in the case of WebSockets, we can draw some lines based roughly on what web browsers (which is the target application developer experience that WebSockets were designed for) support as being high level, and what the WebSocket protocol allows beyond this as low level. I think a Reactive Streams adapter on top of the existing low level API that has feature parity with the JavaScript API for WebSockets offered by browsers is a worthwhile thing to aim for. Would you agree with that? So, for example, the browser APIs offer one error callback for handling errors from both directions. All WebSocket messages handled by the browser APIs are discrete, fixed length in memory, any chunking is handled transparently underneath. Developers in the browser don't have to explicitly send or implement any sending or receiving of the close messages, they just handle the high level concept of the socket being closed, and the browser implements the handshake underneath transparently. I'd say WebSocket proxies would be completely out of scope for a high level API, they definitely would want to deal with splitting messages, custom close handling, very precise error semantics etc, and such a use case would be well served by the current low level API. Regards, James On 26 February 2018 at 22:15, Simone Bordet <simone.bor...@gmail.com> wrote: > James, > > On Mon, Feb 26, 2018 at 4:37 AM, James Roper <ja...@lightbend.com> wrote: > > On the topic of error handling. A high level API doesn't need to report > each > > individual error with sending. > > Depends on what you mean by "high level APIs"... I guess there is no > formal definition of that, as one can even think that the Java socket > APIs are high level. > > > So firstly, it is impossible to report *all* > > errors with sending, since it's impossible to know, once you send a > message > > across the network, whether it got there or not. So if an application > > developer has a requirement to handle errors in sending (and > realistically, > > there is never a business require to handle just the errors that can be > > detected in sending, it's always about handling all errors), do we expect > > them to implement that logic in two places, both on the sending side to > > handle errors that can be detected on the sending side, and then > > additionally write logic to handle errors that can't be detected on the > > sending side (such as, for example, having the remote side send ACKs, and > > track the ACKs on the receiving end)? I doubt it, having to handle logic > on > > both sides is going to mean the application developer has to spend more > time > > implementing boiler plate that could otherwise be spent solving their > actual > > business problem. > > I think you are oversimplifying this. > > If my business is to write a WebSocket Proxy, I may totally want to > care about writing code that handles errors differently whether they > happened on the read side or on the write side, if just for logging > reasons. > > If my business is to write a protocol on top of WebSocket, I may want > to use custom WebSocket error codes (depending on errors) when I close > the connection. > I may want to send a WS message the moment I receive a WebSocket > "close" event from a peer. > > Sure, maybe in 80% of the cases read and write errors are handled the > same way, but as an application writer I would like to have the choice > between using an API that is "lower" level and allows me full > flexibility with respect to the WebSocket protocol, and an opinionated > (correctly opinionated in the majority of the cases) framework that > for some case reduces the API surface at the cost of something else > like flexibility, increased allocation, ecc. > Both have their use cases. > > I believe the JDK net group always stated they went for the first choice. > The lack of the second choice is under discussion, and as I have > already stated may be a matter of resources. > > > If an application developer needs to handle errors in > > sending messages, it would be much simpler for them to treat both types > of > > errors in the same way. And hence, a high level API should expose > detectable > > errors in sending on the receiving end, by cancelling upstream, > terminating > > the WebSocket with an error, and then emitting the error in the > downstream
Re: websockets
the API exposes a "read" Publisher, a way to send > with a "write" Publisher and some Handler/Listener that returns > Publisher (or CompletionStage) for when handling is complete. The > "read" Publisher would complete when the server closes. The Publisher > from the Handler/Listener would complete when all is handled. > > > >> Sure one may recycle buffers transparently. But only if the >> buffer type supports it. io.netty.buffer.PooledByteBufAllocator provides >> instances of io.netty.buffer.ByteBuf. Not java.nio.ByteBuffer. The latter >> one >> does not have any release/free/dispose/etc. methods and semantics. >> > > If the API exposed some Consumer callback to be applied to > buffers that have been written, then such a pool could also be implemented > externally. > > >> P.S. How may purely RS WebSocket APIs are there? If there are some, we >> can probably >> learn from their experience and make things a bit better. Otherwise I >> don't see >> why we should create one more of them. Would you rather have something >> low-level, you could then implement your high-level API that is most >> relevant to >> your case? >> > > You can see the reactive WebSocket API facade (abstracting different > clients) in the Spring Framework: > https://docs.spring.io/spring/docs/current/javadoc-api/org/ > springframework/web/reactive/socket/package-summary.html > > > This is an example of just one way that WebSocket could be modelled. > It appears reasonably straight forward, but restrictive in terms of the > underlying protocol, not exposing partial messages for example, > requiring an additional allocation of a message type for each message > sent / received. I would expect that such a higher-level API could be > implemented on top of the Java SE API without too much work. > > -Chris. > > [1] https://github.com/spring-projects/spring-framework/ > blob/master/spring-webflux/src/main/java/org/springframework/web/reactive/ > socket/WebSocketMessage.java > > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: websockets
If it's a question of resources for building a separate Flow based API on top of the existing API provided, then perhaps we could help? I could certainly implement it outside the JDK in the coming months, would the HTTP client team be willing to consider folding it in as a utility alongside the WebSocket API in the JDK? I know that's not a zero resource thing, but assuming I can produce something reasonable, reviewing it should be cheaper than implementing it. Also I don't know if there's any work being done on a TCK for this, and whether inclusion of this in the JDK would require a TCK to verify it. On 20 February 2018 at 08:41, Simone Bordet <simone.bor...@gmail.com> wrote: > James (hi! :) > > On Fri, Feb 16, 2018 at 12:55 AM, James Roper <ja...@lightbend.com> wrote: > > The question for me then is whether there is value in introducing an > > additional higher level API (ie, a reactive streams based API) for the > > purposes of integration with other libraries that provide RS support. > > I have mixed feeling about this. > > I first tried to push the WebSocket APIs to be Flow-based when they > were slated for JDK 9 official inclusion. > As the WebSocket APIs were moved to incubation and are now slated for > JDK 11, it may be worth considering again whether it's the case to > provide a Flow-based APIs. > > However, Pavel's point about the limits of the Flow APIs still stand, > so a Flow API may be useful, but less generic. > > Another point that makes me feeling uncertain is the good comment by > Konrad Malawski about the design of Akka Streams versus e.g. Spring's > Flowable, where the former does not implement RS or Flow interfaces, > while the latter does. > Now that we have the dichotomy of RS and JDK's Flow, it is easy for > Akka Stream implement methods such as asRSPublisher() and > asFlowPublisher() so that they work in both environments (with - yuck > - multi-release jars). > It would be harder for Spring's Flowable to provide support for both > RS and Flow. > > So at the end I would like to see a *separate* Flow API on top of the > current WebSocket API, but who's going to implement it ? > Would be great to standardize a community effort, but even better if > the JDK provided it. > > > Introducing another API, regardless of what that API is, obviously has > > drawbacks as it increases the surface area of the API, and thus > complexity, > > and also potentially introduces confusion for application developers as > they > > now have two different ways of achieving things, and they need to know > which > > to use when. So, the value of that API must be high enough to outweigh > those > > drawbacks. I strongly believe that a Reactive Streams based API does add > > enough value to do that - and the post that I shared in my previous email > > demonstrates how an ecosystem where reactive streams is embraced can > provide > > a lot of value to application developers. So I'm happy to talk more here > > about use cases and code samples etc if you don't agree here. > > I personally don't question the value of a RS or Flow API. > It's just a matter of resources - we can ask Oracle to do it, but if > they don't have resources to do it, then the road ahead is > complicated. > I see better a Flow wrapper of the JDK WebSocket APIs under the > Reactive Extension (https://github.com/Reactive-Extensions) or > Reactive Streams (https://github.com/Reactive-Streams) umbrella. > That would perhaps have less resource issues and still give a unique > library rather than a gazillion similar wrappers. > > > For signalling errors upstream, in all the cases that I've worked with, > > we've never found a reason that sending the error downstream, and just > > cancelling upstream, is a problem. At the end of the day, there is only > one > > thing that can fail, the TCP connection, and if it fails, both upstream > and > > downstream will fail - there is no such thing as a half failure in TCP. > > HttpServlet.service() allows to throw exceptions that the Servlet > container must handle by running the error page algorithm, which is > user code. > A Subscriber (provided by a library) encounters an exception while > performing its job. > Since it comes from a library, the library itself does not know how to > handle the error because it does not know in what environment it is > being used. > The common solution I saw in RS libraries for this is to provide > library specific APIs that registers a callback that is invoked in > case of errors. > Another common solution is to have an inverse RS that relays errors in > the opposite direction. > With this library specific APIs, an app running in a Servlet Container > can tri
Re: WebSocket
Hi Chuck, Let's just imagine that for a moment that there existed a reactive streams equivalent to an ObjectReader (there isn't, and there's good reason why there isn't, but I'll get to that later). Then, the code would be something like this (the Source API there is an Akka Streams like API): HttpRequest.create(URI.create("...")) .GET() .response((status, headers) -> BodySubscriber.from( Source.asPublisher() .via(ReactiveStreamsObjectReader.create()) .forEach(object -> { // do what you want with each object as its passed here }) ) ); Now that actually looks like less, and simpler, code than your implementation - the above is the full code for the example, you only included code for handling the body. You can also do a lot more than that, you declaratively define how you process your objects, create complex graphs feeding them to other locations, etc. I'd suggest you read the docs on Reactive Streams implementations, because one of the goals of the JDK9 client is to be asynchronous, and being asynchronous means you do have to turn your processing on its heaḋ, you don't pull (ie, you don't invoke readObject and get something back), rather, you are pushed things, so you define stages in your processing, and provide callbacks when you want to do custom things. Here's the docs for Akka streams to get acquainted with an asynchronous streaming view of the world: https://doc.akka.io/docs/akka/2.5/stream/index.html Now, as I said, no reactive streams implementation (that I'm aware of) provides Java serialization support, and for good reason. It's 2018, Java serialization has been shown, time and time again, to have major security flaws in it. If you make an HTTP request on another application, and parse what it gives back to you using ObjectInputStream, you are opening a quite trivial way for that application to execute code on your application. Even if you trust the remote system, it goes against the security best practice of bulkheading - ensuring that if one application is compromised, the entire system isn't compromised. No one wants to provide support these days for such insecure practices. Here's a good summary of why you should never use Java serialization: https://www.christian-schneider.net/JavaDeserializationSecurityFAQ.html Regards, James On 18 February 2018 at 20:12, Chuck Davis <cjgun...@gmail.com> wrote: > There is a great hush here regarding strategy for putting the pieces > back together so here goes my tirade. > > Following is based on assumption of a 100k payload. > > With jdk8: > The decoder received a ByteBuffer > bais = new ByteArrayInputStream(ByteBuffer.array()); // wrap the > buffer -- optional method but implemented in Oracle JVM > ois = new ObjectInputStream(bais) // wrap the input stream > MyDTO = ois.readObject(); // deserialize the object (which may > contain hundreds or thousands of objects) > > DONE! Simple, clean and blazingly fast. > > With jdk9: > The Listener receives a ByteBuffer (I read someplace the default size > is potentially going to be 256 bytes) > If it's the first part (or whole) create a byte[] of 10k (or whatever) > Start copying bytes from the buffer to the array > Check each byte to be sure there is room in the array for another byte > if room, copy the byte > if no room, copy the array to a larger array (current size + 10k) > and then copy the byte > repeat until all messages are processed (i.e. each byte will have > been copied somewhere between 1 and 10 times) > bais = new ByteArrayInputStrea(byte[]); > ois = new ObjectInputStream(bais); > MyDTO = ois.readObject(); > > DONE! More complicated, messy and very slow. (i.e. lots of wasted cpu > cycles) > > RESULT: The formerly fabulous WebSocket has been rendered relatively > useless as a platform for building responsive, bidirectional > client/server applications. Am I the only person who sees this as a > HUGE regression of functionality?? I am ALARMED by this turn of > events. > > OPINION: > > I'm pretty naive about the world of midrange and mainframe except that > they can throw a lot of bytes in a short time. But I imagine the > choke-point of any desktop application is going to be the network. > Unless somebody is running a Pentium I doubt any relatively modern > desktop is going to have a difficult time keeping up with networking > speeds. It seems to me, therefore, that backpressure in WebSocket is > a solution looking for a problem. If backpressure is somehow > essential in WebSocket include the possibility but please don't > destroy the best client/server communication in the process. If > necessary, create two implementations: WebSocketFast and > WebSocketSlow. > > I'll go back to my cave now and pout about what has
Re: websockets
. If one side decides to restart, the otherside must restart too. They are already coupled, and cannot be decoupled. Introducing coupling in the error handling simplifies the handling, because it exposes what is already true. The creation of additional objects for representing message types is of course an overhead, but one that users of the high level API would be happy to have given the advantages of not having to implement their own integration code. The JVM is highly optimised to handle the creation and garbage collection of short lived objects, and this allows high level APIs like Reactive Streams to exploit this, allowing simpler programming models without having to forgo a huge amount of performance. Of course, that doesn't mean we needlessly create objects for no reason, but in this case I believe the benefits of being able to seamlessly integrate with other streaming libraries outweigh the overhead of an allocation or two per message. Having one stream per message type would cause problems - for example, the socket.io protocol encodes binary messages by first sending a text message containing metadata, which is then followed by one or more binary messages. Sending this in multiple different streams would make correlation of those binary messages with the text header very difficult if not impossible. Regards, James On 12 February 2018 at 16:33, Pavel Rappo <pavel.ra...@oracle.com> wrote: > Hello James, > > Thanks for the comprehensive reply to Chuck's email. Now regarding your > own email. > > > On 10 Feb 2018, at 07:38, James Roper <ja...@lightbend.com> wrote: > > > > > > > > But that brings me to a problem that I'd like to give as feedback to the > implementers - this API is not Reactive Streams, and so therefore can't > take advantage of Reactive Streams implementations, and more problematic, > can't interop with other Reactive Streams sinks/sources. If I wanted to > stream a WebSocket into a message broker that supports Reactive Streams, I > can't. I would definitely hope that Reactive Streams support could be added > to this API, at a minimum as a wrapper, so that application developers can > easily focus on their business problems, plumbing and transforming messages > from one place to another, rather than having to deal with implementing > concurrent code to pass messages. > > > > I totally understand your concern over the lack of a Reactive Streams > interface > to this low-level API. All I can say is that it's not that we haven't > tried. As > usual the devil is in the detail. There were at least a couple of major > issues > we couldn't find a satisfactory solution to. > > The first one is how to communicate errors back to the user's code that > publishes messages to WebSocket. This issue has been extensively discussed > here [1]. > The only signal the publisher can receive from a subscriber in the case of > a > failure is a cancellation signal. After this signal the subscription is > considered cancelled. Now, there are different solutions to this problem, > but > none of the ones we looked at seemed comprehensive. For example, such > errors > can be propagated by the WebSocket to the user's subscriber receiving > messages. > In which case WebSocket input and output will become tied and the WebSocket > client will seem to require both the publisher and the subscriber to be > present > at the same time. > > The second issue is how to communicate completion signals from processing > individual messages. That might be handy in the case the implementation or > the > application decide to recycle buffers they use. With a naive RS interface > to > WebSocket all the user's publisher can receive from the WebSocket's > subscriber > is a request for a number of extra messages. Using only this number the > publisher cannot deduce which of the previously published messages have > been > actually sent. This problem also has a number of solutions. One of which > would > be to use more streams. An extra publisher WebSocket would use to emit > outcomes > of sending messages and an extra subscriber WebSocket would use to receive > signals from the user once a message has been received. To be honest it > looks > cumbersome. > > There are also a dozen of smaller issues that have to be resolved before > creating a well-defined RS interface to WebSocket. > > > It may well require wrapping messages in a high level object - text, > binary, ping, pong, etc, to differentiate between the message types. > > > > We went great lengths in supporting different kinds of requirements in > this API. > One of the such requirements that arose from a number of discussions on > this > mailing list was not to force unnecessary allocations, garbage
Re: websockets
On 12 February 2018 at 18:56, Chris Hegarty <chris.hega...@oracle.com> wrote: > James, > > On 10/02/18 07:38, James Roper wrote: > >> ... >> >> https://developer.lightbend.com/blog/2018-02-06-reactive-str >> eams-ee4j/index.html >> > > Regarding: https://github.com/jroper/reactive-streams-servlet/blob/7a2a > 651b706bb0612f6d11311e442f82ce307ed2/reactive-streams-servle > t/src/main/java/org/reactivestreams/servlet/RequestPublisher.java > > If I'm not mistaken, this appears to be mainly an adapter > from Publisher of InputStream to Publisher of ByteBuffer. > Did you overlook HttpRequest.BodyPublisher > fromInputStream(Supplier streamSupplier)? > Or did you run into some issue with it? Hi Chris, Not quite. RequestPublisher is an adapter of ServletInputStream to Publisher, and the big difference here is that ServletInputStream has a mechanism (introduced in Servlet 3.1) for doing non blocking IO, which is handled by registering a ReadListener, which is not part of the InputStream interface, but is part of the ServletInputStream interface. Using this, you use ServletInputStream.isReady to see if the stream is ready to return data from ServletInputStream.read without blocking. If it is, you invoke read, otherwise, you wait until ReadListener.onDataAvailable() is invoked, and then you can invoke ServletInputStream.read without blocking again. So the HttpRequest.BodyPublisher.fromInputStream, I presume, does blocking IO on the InputStream (since InputStream only supports blocking IO), using the blocking InputStream.read call, while RequestPublisher does non blocking IO on ServletInputStream. Cheers, James > > -Chris. > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: websockets
; } > }; > > The next message will be requested after the previous one has been echoed > back > to the server. In this example there is zero copying from the API user's > perspective. > > Practically speaking, could it be any more asynchronous? > > Mind you, going one-by-one (i.e. request(1) after consumption) is not the > only > possibility. Back-pressure provides quite flexible control over > consumption. > Have a look at java.util.concurrent.Flow. The spec for this type provides > a very > nice example (SampleSubscriber) of buffering. > > Chuck, if you still think this API is missing some crucial capability, I > will > kindly ask you to provide an example of an application that is possible to > implement in a non-back-pressured model and is not possible in the proposed > WebSocket API. > > Thanks, > -Pavel > > --- > [1] https://github.com/reactive-streams/reactive-streams-jvm/blob/ > 3716ba57f17e55ec414aed7ccb1530251e193b61/README.md#goals-design-and-scope > [2] One does not have to signal the message has been processed in order to > receive the next message, if there are any. The signal is about > handing over > the message contents, not about the back-pressure. > > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: websockets
Hi Chuck, Presumably this API is similar in intention to the request method used in reactive streams (aka java.util.concurrent.Flow), that is, request is the means by which backpressure is propagated. One major problem with JDK8 WebSockets is there's no way to asynchronously propagate backpressure, you have to accept every message that comes as it comes, you can't tell the other end to back off, which means if it's producing messages faster than you can consume them, your only two options are to fail fast, or risk running out of memory. Reactive Streams solves this by requiring consumers to signal demand for data/messages before they receive any - an invocation of the request method is just saying how many more elements the consumer is currently ready to receive, and can be invoked many times, as the consumer processes messages and is ready to receive more. Generally, for Reactive Streams, application developers are not expected to implement or invoke these APIs directly, instead, they are expected to use reactive streams implementations like Akka Streams, rxJava or Reactor, which efficiently manage buffering and keeping the buffer at an appropriate level for the application developer, so the application developer can just focus on their business concerns. But that brings me to a problem that I'd like to give as feedback to the implementers - this API is not Reactive Streams, and so therefore can't take advantage of Reactive Streams implementations, and more problematic, can't interop with other Reactive Streams sinks/sources. If I wanted to stream a WebSocket into a message broker that supports Reactive Streams, I can't. I would definitely hope that Reactive Streams support could be added to this API, at a minimum as a wrapper, so that application developers can easily focus on their business problems, plumbing and transforming messages from one place to another, rather than having to deal with implementing concurrent code to pass messages. It may well require wrapping messages in a high level object - text, binary, ping, pong, etc, to differentiate between the message types. For a broader context of how this would fit into the broader Reactive Streams ecosystem, I've published a blog post of what it would look like if Java EE/EE4J were to adopt Reactive Streams everywhere, as it happens this also includes proposals for using Reactive Streams in the JSR 356 WebSocket spec: https://developer.lightbend.com/blog/2018-02-06-reactive-streams-ee4j/index.html Regards, James On 9 February 2018 at 19:16, Chuck Davis <cjgun...@gmail.com> wrote: > I've been using jdk8 websockets to develop my desktop java > applications. Now that jdk9 is on my machine I started looking at > websockets and I'm not at all sure I like what I see. Can someone > familiar with this feature please explain the rationale for what is > happening? > > I'm concerned, at this initial stage, primarily by > WebSocket.request(long). This "feature" seems to have at least two > very negative impacts: > > 1) It appears to destroy the asynchronous nature of websockets; > 2) It appears to place programmers in the impossible position of > guessing how many messages the server side might send. > > 1) If everything has to stop while the client asks for more messages > asynchronous communication is nullified. The jdk8 implementation is, > therefore, much more functional. > > 2) It would appear the only logical use of WebSocket.request() would > be to use a long value of Long.MAX_VALUE since there is no way to know > how many messages may be received. And what if the programmer asks > for 1 message and the next message has 3 parts. We're screwed. > Additionally, the documentation specifically states that the > WebSocket.Listener does not distinguish between partial and whole > messages. The jdk8 implementation decoders/encoders accumulate > messages and assemble them until the message is complete and then pass > it to the Endpoint -- much more satisfactory arrangement. > > I cannot fathom the meaning or improvement of this new wrinkle. > > Thanks for any insight > -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>
Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher
Hi Chris, This looks like a straight forward way to solve the problem with minimal disruption from the existing API. Can I make a few suggestions though? We could add a contentLength parameter to fromPublisher, to allow Flow.Publishers where the content length is known to be easily converted to BodyPublisher: static BodyPublisher fromPublisher(Flow.Publisher publisher, int contentLength) { ... } This would mean if you were receiving a servlet request body and publishing it to another location, then you could do something like this (this uses a reactive streams implementation on top of the servlet API that I wrote): HttpServletRequest request = ... long contentLength = -1; if (request.getHeader("Content-Length") != null) { contentLength = Long.parseLong(request.getHeader("Content-Length")); } Publisher publisher = new RequestPublisher(request.startAsync(), 8192); HttpRequest clientRequest = HttpRequest.newBuilder(target) .POST(BodyPublisher.fromPublisher(publisher, contentLength)) .build() Perhaps the method could be overloaded for both supplying and not supplying a content length. Similarly, I think a fromSubscriber API that accepted a CompletionStage would be a little more fluent than having to supply it externally: public static BodyHandler fromSubscriber(Subscriber> subscriber, CompletionStage bodyFuture) { ... } Then you could have something like this: TextSubscriber subscriber = ...; // accumulates bytes and transforms them into a CompletionStage. CompletionStage result = subscriber.getTextResult(); CompletableFuture cf = client .sendAsync(request, BodyHandler.fromSubscriber(subscriber, result)); String text = cf.join(); Likewise, this could be an overload of fromSubscriber if we want the option of not specifying a body future. One thing I think needs to be carefully specified is, if the method doesn't accept a CompletionStage, when/how the CompletionStage returned from send is redeemed. Regards, James On 9 December 2017 at 04:31, Chris Hegarty <chris.hega...@oracle.com> wrote: > James, > > Thanks for taking the time to look at this, and sending your thoughts. > > On 08/12/17 00:30, James Roper wrote: > > Hi all, > > > > I wanted to start a discussion about the use of Flow.Subscriber and > > Flow.Publisher in JEP 321 (HTTP Client API). > > > > It seems that users are required to implement their own publishers and > > subscribers, that is, they can't take a Flow.Publisher or > > Flow.Subscriber provided by another reactive streams implementation, and > > pass it on to the HttpClient API. The reason for this is that the > > HttpClient API doesn't accept Flow.Publisher/Flow.Subscriber, rather it > > extends them in HttpRequest.BodyPublisher and > > HttpResponse.BodySubscriber, and then requires the user to return > > instances of those sub interfaces from their BodyHandlers. ... > > Great point. I think we can address this with straight forward adapters. > For example: > > public interface BodyPublisher extends Flow.Publisher { > > /** > * Returns a request body publisher whose body is retrieved from the > * given {@code Flow.Publisher}. The returned request body publisher > * has an unknown content length. > * > * @apiNote This method can be used as an adapter between {@code > * BodyPublisher} and {@code Flow.Publisher}. > * > * @param publisher the publisher responsible for publishing the body > * @return a BodyPublisher > */ > static BodyPublisher fromPublisher(Flow.Publisher > publisher) { > ... > } > > ... > >public BodySubscriber apply(int statusCode, HttpHeaders > responseHeaders); > > /** > * Returns a response body handler that returns a {@link > BodySubscriber > * BodySubscriber}{@code } obtained from {@link > * BodySubscriber#fromSubscriber(Subscriber)}. > * > * @apiNote This method can be used as an adapter between {@code > * BodySubscriber} and {@code Flow.Subscriber}. > * > * For example: > * {@code > * TextSubscriber subscriber = ...; // accumulates bytes and > transforms them into a String. > * Supplier result = subscriber::getTextResult; > * > * CompletableFuture cf = client > * .sendAsync(request, BodyHandler.fromSubscriber(sub > scriber)) > * .thenApply((response -> result.get())); > * String text = cf.join(); > * } > * > * @param subscriber the subscriber > * @return a response body handler > */ > public static BodyHandler fromSubscriber(Subscriber List> subscriber) { > ... > } > > // Add an equiv
JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher
Hi all, I wanted to start a discussion about the use of Flow.Subscriber and Flow.Publisher in JEP 321 (HTTP Client API). It seems that users are required to implement their own publishers and subscribers, that is, they can't take a Flow.Publisher or Flow.Subscriber provided by another reactive streams implementation, and pass it on to the HttpClient API. The reason for this is that the HttpClient API doesn't accept Flow.Publisher/Flow.Subscriber, rather it extends them in HttpRequest.BodyPublisher and HttpResponse.BodySubscriber, and then requires the user to return instances of those sub interfaces from their BodyHandlers. Let's say I have a database driver that produces a Flow.Subscriber for consuming a stream to be stored in the database, and I want to plumb a response body into that database subscriber. I can't return this from an HttpResponse.BodyHandler it requires me to return a HttpResponse.BodySubscriber, not a Flow.Subscriber. Of course, users can implement their own HttpResponse.BodySubscriber that delegates onSubscribe/onNext/onComplete/onError to the databases Flow.Subscriber, but needing to do this every time does not provide a great developer experience. In order for reactive streams implementations to integrate with each other, Flow.Subscriber and Flow.Publisher should only be extended if the implementation is providing its own implementation of that interface and doesn't expect end users to implement it. For cases where an implementation expects users either to pass that sub interface, or return it from a method to that they implement as is the case for BodyHandler, then it has to be a Flow.Subscriber or Flow.Publisher, not a sub interface. So perhaps HttpResponse.BodySubscriber should be modified to, rather than extending Flow.Subscriber, have a getSubscriber() method that returns a Flow.Subscriber. Alternatively, the API could be inverted, for example, HttpResponse.BodyHandler.apply could be modified to take three parameters, the status code, the HttpHeaders, and a Flow.Publisher, and the return value could be CompletionStage. This approach would actually maximise interoperability, since a lot of reactive streams implementations are publisher centric. For example, to my knowledge RxJava only provides limited support for creating and transforming data using a Flow.Subscriber, whereas if you give RxJava a Flow.Publisher, you can then do the full range of operations (eg map) that RxJava supports on that stream (someone with a better understanding of RxJava correct me if I'm wrong). There's probably many other solutions, these are just two that I've thought of. There is also a broader discussion that needs to be had in the reactive streams community over whether there should be a bias towards everything returning/accepting publishers, or if implementations should evenly support both. Supporting one or the other arbitrarily will have interoperability implications, and while it's definitely beyond the scope of JEP 321 to decide on that, it's something that we should keep in mind. Regards, James -- *James Roper* *Senior Octonaut* Lightbend <https://www.lightbend.com/> – Build reactive apps! Twitter: @jroper <https://twitter.com/jroper>