Hi Sergey,
NioBodyContext is mean as a parallel to ContainerRequest/ResponseContext
and Reader/WriterInterceptorContext; it looks slightly better compared
to former version (look at MessageBodyReader/Writer).
Visual look is not the only reason - its much simpler to add something
into a context compared to introducing a new parameter to the method
(which is implemented by application code). According to Java EE
"rules", the latter is impossible to do without overloading.
Pavel
On 15/03/2017 11:37, Sergey Beryozkin wrote:
> Hi Pavel
>
> We have input and output streams being replaced in a number of
> security related filters and interceptors. This feature of being able
> to replace the streams is very important.
>
> I agree we should be careful with keeping introducing new interfaces.
> Speaking of the new interfaces, what is the idea behind NioBodyContext ?
>
> Thanks, Sergey
>
> On 15/03/17 07:42, Pavel Bucek wrote:
>> User won't need to rewrite anything, since JAX-RS will stay backwards
>> compatible. They may choose to do so. It will be slightly harder (and
>> please note that this is definitely an understatement) for all
>> implementations, if we impose a rule which will force to have all
>> processing done in non-blocking manner.
>>
>> I was wondering what is the common usecase for Intercetors and
>> Filters, so I looked up some usages in Jersey (I know it is not a
>> good reference, but at least something to start with):
>>
>> Request/Reader
>> SecurityRequestFilter (and variants to that)
>> - sets security context
>> - can touch headers, uri, even entity, when there is a need
>> for signature verification etc)
>> - can abort whole request processing (producing response
>> directly)
>> PatchingIntercetor
>> - handles HTTP Patch, needs to consume whole entity
>> (rest is test-only)
>>
>> Response/Writer
>> LoggingInterceptor
>> - wraps the entity stream to be able to log it
>> - modifies headers
>> TemplateMethodInterceptor
>> - MVC processing. Sets entity as an object.
>> JsonWithPaddingInterceptor
>> - the old, deprecated "jsonp"; wraps messages in javascript
>> callbacks
>>
>> Combined
>> LoggingFilter
>> - modifies headers
>> ContentEncoder (Deflate, Gzip)
>> - encodes/decodes an entity
>> - modifies headers
>>
>> and the answer, just based on those, is "pretty much everything". My
>> thinking was that if we won't need to access something, we might be
>> able to make the interface simpler or possibly just a @Provider
>> annotated processor, but it doesn't seem likely. Anyway, I'd be happy
>> to read any comments, maybe it would make sense to make non-blocking
>> filter/interceptor simpler and separate it from the current flow.
>>
>> The counterargument for introducing new interfaces is that the
>> processing might become too complicated.
>>
>> Currently, we do already have ReaderInterceptor, WriterInterceptor,
>> ContainerRequestFilter, ContainerResponseFilter, MessageBodyReader,
>> MessageBodyWriter, ClientRequestFilter, ClientResponseFilter.
>>
>> Non-blocking proposal at this moment adds NioBodyReader,
>> NioBodyWriter and modifies some "contexts" to be able to work with
>> processors, not just streams. Introducing another two of four new
>> interfaces might be an overkill. On the other hand, if we say
>> something like "the old interfaces are 'semi-deprecated'", it could
>> be ok.
>>
>> I will definitely work on putting together some integration examples,
>> so we can see how useful it will be to have flow api but distributed
>> as part of jax-rs.
>>
>> Any comments welcomed :)
>>
>> Best regards,
>> Pavel
>>
>>
>> On 13/03/2017 21:36, Markus KARG wrote:
>>>
>>> One issue we should really take care of is that all possibilities
>>> one currently has with blocking filters have to be possible with NIO
>>> filters still. A second issue is that filter programmers possibly do
>>> not want to completely rewrite all existing code to support NIO. The
>>> original filter API has only one drawback which is entity stream
>>> being an InputStream / OutputStream. All the rest is great already.
>>> So while I understand that reshaping filters as Flow.Processors is a
>>> future-proof idea, following that new design approach needs lots of
>>> features to get reinvented. Possibly it might be better if we simply
>>> stick with the existing filters to have all that existing features,
>>> and then just *optionally* allow them to implement Flow.Processor as
>>> an indicator to the runtime that this existing (old-school) filter
>>> is able to support NIO. That way, existing filters can be enhanced
>>> to support NIO in new environments but fall back to blocking IO in
>>> old environments. The sole question would be, how such a filter
>>> could notice that it runs in an NIO environment so it would not try
>>> to access the InputStream / OutputStream.
>>>
>>> -Markus
>>>
>>> *From:*Santiago Pericas-Geertsen
>>> [mailto:santiago.pericasgeertsen_at_oracle.com]
>>> *Sent:* Montag, 13. März 2017 18:47
>>> *To:* jsr370-experts_at_jax-rs-spec.java.net
>>> *Subject:* Re: [jax-rs-spec users] NIO API review / request for feedback
>>>
>>> Hi Pavel,
>>>
>>> Good questions.
>>>
>>> @Provider
>>>
>>> *public static class *NioInputFilter *implements
>>> *Flow.Processor<ByteBuffer, ByteBuffer> {
>>>
>>> /// .../
>>>
>>> //
>>>
>>> }
>>>
>>> Yes, or possibly defining an interface that in turn implements
>>> Flow.Processor.
>>>
>>>
>>>
>>> how would we pass the executor service (we could do constructor
>>> injection..) or how could we plug-in 3rd party framework? I do see a
>>> typical case of creating Flowables etc from existing
>>> Publisher/Source - what would we need to do? Wrap it? Wouldn't that
>>> be typical usecase?
>>>
>>> I haven’t seen enough use cases of 3rd party integration to be
>>> comfortable answering this. However, I would imagine most of this
>>> integration would take place in the resource method rather than a
>>> filter. Otherwise, wrapping could be an option.
>>>
>>>
>>>
>>> @Provider
>>> *public static class *NioInputFilter *implements
>>> *Flow.Processor<ByteBuffer, ByteBuffer> {
>>> Flowable<ByteBuffer> *flowable *= *null*; /// .../
>>> //
>>> //_at_Override
>>> *public void *subscribe(Flow.Sink<? *super *ByteBuffer> sink) {
>>> *flowable*.subscribe(...)
>>> }
>>> /// etc/
>>> }
>>>
>>>
>>> Also, would we be able to select which request is going to be
>>> filtered based on matched resource method, header value, ...?
>>> Wouldn't we need something like "ContainerRequestFilter" to control
>>> when this is added to the flow pipeline?
>>>
>>> Yes, we would need to figure out which kinds of filters make sense
>>> for NIO. Not sure yet.
>>>
>>> How we can add a header?
>>>
>>> I’d imagine you’d need a context, just like in the current API. As
>>> I said above, we could use a subtype of Flow.Processor.
>>>
>>> In any case, needs more thought, but it jumped at me as something
>>> that would fit better in the existing JAX-RS API.
>>>
>>> — Santiago
>>>
>>> On 13/03/2017 15:52, Santiago Pericas-Geertsen wrote:
>>>
>>> Hi Pavel,
>>>
>>> Hi Markus,
>>>
>>> seems like you indeed missed it, but this one is easy to miss - it's only about single method for now, called "addProcessor".
>>>
>>> Please seehttps://github.com/pavelbucek/jax-rs/blob/nio/examples/src/main/java/jaxrs/examples/nio/ServerSideProcessing.java
>>>
>>> There are several methods added on Filters and interceptors, which add the similar functionality as are added for resource methods and Body Readers/Writers.
>>>
>>> The pattern used in filters and interceptors feels a bit strange, and perhaps not inline with the rest of the API. In particular,
>>>
>>> static class RequestInterceptor implements ReaderInterceptor {
>>>
>>> @Override
>>>
>>> public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
>>>
>>> // we might need an Executor/ExecutorService when creating a processor
>>>
>>> context.addProcessor(PROCESSOR);
>>>
>>> return context.proceed();
>>>
>>> }
>>>
>>> }
>>>
>>> is not really filtering, but instead it is constructing a “flow” pipeline by adding one processor. I think this is because, even though we defined NIO readers/writers, we are trying to re-use the existing non-NIO filters and interceptors.
>>>
>>> If I need a NIO filter, why not define one directly (a type extending processor) and associate it with the NIO resource method as we do with normal filters? I see challenges doing this too, but it seems more natural to me.
>>>
>>> Thoughts?
>>>
>>> — Santiago
>>>
>>> Regards,
>>>
>>> Pavel
>>>
>>> On 10/03/2017 18:16, Markus KARG wrote:
>>>
>>> Pavel,
>>>
>>>
>>>
>>> maybe I missed it, but is it planned to also provide a non-blocking replacement for the InputStream / OutputStream methods of Filters and other streaming components?
>>>
>>>
>>>
>>> Thanks
>>>
>>> -Markus
>>>
>>>
>>>
>>>
>>>
>>> From: Pavel Bucek [mailto:pavel.bucek_at_oracle.com]
>>>
>>> Sent: Dienstag, 7. März 2017 15:26
>>>
>>> To:jsr370-experts_at_jax-rs-spec.java.net
>>> <mailto:jsr370-experts_at_jax-rs-spec.java.net>
>>>
>>> Subject: NIO API review / request for feedback
>>>
>>>
>>>
>>> Dear EG members,
>>>
>>> please allow me to share the direction of what we are thinking about JAX-RS NIO support.
>>>
>>> As stated before, SSE wasn't the only place were Flow APIs should be utilized - NIO is another area where it can be utilized quite heavily. And that was mostly the main idea - to see where it does make sense to use that.
>>>
>>> Similarly to SSE, there is a plan to minimize / extract the Flow to different classes, but there should always be a clear path how to convert an instance into Flow.* (or org.reactivestreams.*) interfaces. It is not done yet, to keep things as clear as possible.
>>>
>>> One of the bigger concerns is backwards compatibility. There are interfaces, which do work directly with Input/Output stream, which is always a problem for reactive processing, since it is blocking by design. The specification will need to say something like "The runtime is non-blocking as long as the application code doesn't read from a provided InputStream or doesn't set an OutputStream."
>>>
>>> The motivation for doing this is to allow the JAX-RS apps to be non-blocking and reactive. JAX-RS 2.0 entity handling is designed around reading / producing Input / Output Stream, which is blocking by design. Non-blocking approach should result in higher throughput and better resource utilization of the server. Also, integration and coexistence with modern reactive frameworks should be possible to do without losing the advantage of having that framework (which was almost completely lost when dealing with blocking inputs/outputs).
>>>
>>> Let's jump into code snippets.
>>>
>>>
>>>
>>> Server - EX1 (byte handling):
>>>
>>> Snippet below shows how to process request entity body return response entity body, using Publisher<ByteBuffer> - no MessageBodyReader/Writer is involved. This can be used as a low-level integration point for other frameworks, which are also reactive and will do the processing, like serializing/deserializing (mapping) to some java type, filtering, etc. Returning a Publisher<ByteBuffer> is a reactive/nio alternative to javax.ws.rs.core.StreamingOutput, consuming an entity using Publisher<ByteBuffer> is a reactive/nio alternative to consuming entity as an InputStream.
>>>
>>> @POST
>>>
>>> @Path("/ex1")
>>>
>>> public Flow.Publisher<ByteBuffer> ex1(Flow.Publisher<ByteBuffer> entity) {
>>>
>>> Ex1Processor processor = new Ex1Processor();
>>>
>>>
>>>
>>> entity.subscribe(processor);
>>>
>>> return processor;
>>>
>>> }
>>>
>>>
>>>
>>> // ex1 processor
>>>
>>> public static class Ex1Processor implements Flow.Processor<ByteBuffer, ByteBuffer> {
>>>
>>>
>>>
>>> // ...
>>>
>>> }
>>>
>>> And there is already an issue, which is not clearly solved.
>>>
>>> Returning a Publisher instance from the resource method does put some constraints on the Publisher itself - it needs to cache all events which will be emitted prior subscription of the jax-rs implementation Subscriber instance (which is the only way how to get the data from a Publisher).
>>>
>>> This can be solved by stating that request entity publisher won't produce any events until the resource method is invoked and the implementation Subscriber subscribed to the returned response entity publisher. Or the resource method can return Consumer<Flow.Subscriber<ByteBuffer>>, which would effectively grant control of the implementation subscription process. Any comments or suggestions welcomed [ref Q1].
>>>
>>>
>>>
>>> Server - EX2 (consuming stream of pojos):
>>>
>>> The next example should be slightly more straightforward. It shows how to process a Publisher of custom type, in this case called "POJO".
>>>
>>> The only limitation or rule here would be that the subscriber for the request entity must be subscribed before the resource method "ex2" invocation ends.
>>>
>>> New interface is introduced here - NioBodyReader. It has exactly the same responsibility as good old MessageBodyReader, but without using a blocking OutputStream to write the entity. Note that the "core" type is the Publisher<ByteBuffer>, which is in this case mapped (or converted) into Publisher of POJOs.
>>>
>>> @POST
>>>
>>> @Path("/ex2")
>>>
>>> @Consumes(MediaType.APPLICATION_JSON)
>>>
>>> public void ex2(Flow.Publisher<POJO> entity,
>>>
>>> @Suspended AsyncResponse response) {
>>>
>>>
>>>
>>> // TODO: introduce a helper or modify AsyncResponse to support this pattern directly?
>>>
>>> entity.subscribe(
>>>
>>> // POJO subscriber - consumer
>>>
>>> new Flow.Subscriber<POJO>() {
>>>
>>> @Override
>>>
>>> public void onSubscribe(Flow.Subscription subscription) {
>>>
>>> // ...
>>>
>>> }
>>>
>>>
>>>
>>> @Override
>>>
>>> public void onNext(POJO item) {
>>>
>>> // ...
>>>
>>> }
>>>
>>>
>>>
>>> @Override
>>>
>>> public void onError(Throwable throwable) {
>>>
>>> response.resume(throwable);
>>>
>>> }
>>>
>>>
>>>
>>> @Override
>>>
>>> public void onComplete() {
>>>
>>> response.resume(Response.ok().build());
>>>
>>> }
>>>
>>> }
>>>
>>> );
>>>
>>> }
>>>
>>>
>>>
>>> @Provider
>>>
>>> @Consumes(MediaType.APPLICATION_JSON)
>>>
>>> public static class Ex2NioBodyReader implements NioBodyReader<POJO> {
>>>
>>> @Override
>>>
>>> public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
>>>
>>> return true;
>>>
>>> }
>>>
>>>
>>>
>>> @Override
>>>
>>> public Flow.Publisher<POJO> readFrom(Flow.Publisher<ByteBuffer> entity,
>>>
>>> Class<POJO> type,
>>>
>>> Type genericType,
>>>
>>> Annotation[] annotations,
>>>
>>> MediaType mediaType,
>>>
>>> MultivaluedMap<String, String> httpHeaders) {
>>>
>>> Ex2MappingProcessor mappingProcessor = new Ex2MappingProcessor();
>>>
>>> entity.subscribe(mappingProcessor);
>>>
>>> return mappingProcessor;
>>>
>>> }
>>>
>>> }
>>>
>>>
>>>
>>> // mapping Publisher<ByteBuffer> to Publisher<POJO>
>>>
>>> // ByteBuffers are expected to contain JSON (indicated by @Consumes on the resource method and NioBodyReader).
>>>
>>> public static class Ex2MappingProcessor implements Flow.Subscriber<ByteBuffer>, Flow.Publisher<POJO> {
>>>
>>> // ...
>>>
>>> }
>>>
>>> Same issue as [Q1] is valid for this as well - same solution will need to be applied for "readFrom" method.
>>>
>>> Another issue is about what should be passed to "isReadable" method as "type" parameter. I'm not exactly sure whether we can safely obtain generic type of a parameter from the resource method (public void ex2(Flow.Publisher< POJO> entity, ..)). Any comments/suggestions welcomed [ref Q2].
>>>
>>> Note that using @Suspended shouldn't be enforced here; it should be possible to return a Response directly and still be able to consume the requestentity.
>>>
>>>
>>>
>>> Server - EX3 (producing list of POJOs):
>>>
>>> The last (for now) example shows how we can produce and write POJOs. Resource method doesn't take any parameters and provides a Publisher of POJO objects, which will be converted to JSON in NioBodyWriter. NioBodyReader is a reactive alternative to MessageBodyReader from older version of the specification.
>>>
>>> @GET
>>>
>>> @Path("/ex3")
>>>
>>> @Produces(MediaType.APPLICATION_JSON)
>>>
>>> public Flow.Publisher<POJO> ex3() {
>>>
>>>
>>>
>>> Flow.Publisher<POJO> pojoPublisher = null;
>>>
>>>
>>>
>>> // source of the POJO "stream" can be anything - database call, client call to
>>>
>>> // another service, ...
>>>
>>> //
>>>
>>> // DB
>>>
>>> // .getEmployees(department) // StreamPublisher<EmployeeDbModel> -- reactive stream
>>>
>>> // .map((Function<EmployeeDbModel, EmployeeToReturn>) employeeDbModel -> {
>>>
>>> // // ...
>>>
>>> // });
>>>
>>>
>>>
>>> return pojoPublisher;
>>>
>>> }
>>>
>>>
>>>
>>> @Provider
>>>
>>> @Produces(MediaType.APPLICATION_JSON)
>>>
>>> public static class Ex3NioBodyWriter implements NioBodyWriter<POJO> {
>>>
>>> @Override
>>>
>>> public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
>>>
>>> return true;
>>>
>>> }
>>>
>>>
>>>
>>> @Override
>>>
>>> public void writeTo(Flow.Publisher<POJO> entityObjectPublisher,
>>>
>>> Flow.Subscriber<ByteBuffer> subscriber,
>>>
>>> Class<?> type,
>>>
>>> Type genericType,
>>>
>>> Annotation[] annotations,
>>>
>>> MediaType mediaType,
>>>
>>> MultivaluedMap<String, Object> httpHeaders) {
>>>
>>> // map Publisher<POJO> to Publisher<ByteBuffer> and subscribe Flow.Subscriber<ByteBuffer> to it.
>>>
>>> }
>>>
>>> }
>>>
>>> Resource method is minimalistic, [Q1] applies here as well.
>>>
>>> The example introduces NioBodyWriter and its isWriteable method does have [Q2], similarly to NioBodyReader. #writeTo doesn't have any issues - [Q1] is mitigated there because the implementation passes a supplier to the implementation - there doesn't need to be anything returned. Something similar might be able to do for NioBodyWriter as well.
>>>
>>>
>>>
>>> Comment to writing multiple POJO instances:https://github.com/pavelbucek/jax-rs/blob/bfc5b3d6caecab2f6304f92ac7b44a7ad6a5fdff/jaxrs-api/src/main/java/javax/ws/rs/ext/NioBodyWriter.java#L82
>>>
>>> Important point to mention is that even when producing multiple instances, the intention here is still to return the single HTTP response.
>>>
>>> ===
>>>
>>> We have more, but this email is already too long - I will post more after there is some feedback on the presented concepts and issues. Please let us know if this format is OK or if you'd prefer something else - I guess I could do a screencast, hangout or something similar.
>>>
>>> Source links:
>>>
>>> - complete server example:https://github.com/pavelbucek/jax-rs/blob/bfc5b3d6caecab2f6304f92ac7b44a7ad6a5fdff/examples/src/main/java/jaxrs/examples/nio/NioResource.java
>>>
>>> - client (to be discussed):https://github.com/pavelbucek/jax-rs/blob/bfc5b3d6caecab2f6304f92ac7b44a7ad6a5fdff/examples/src/main/java/jaxrs/examples/nio/NioClient.java
>>>
>>> - server side processing (including interceptors):https://github.com/pavelbucek/jax-rs/blob/bfc5b3d6caecab2f6304f92ac7b44a7ad6a5fdff/examples/src/main/java/jaxrs/examples/nio/ServerSideProcessing.java
>>>
>>> (Not including direct link to individual examples, since we will continue working on them...)
>>>
>>> Looking forward to your feedback!
>>>
>>> Thanks and regards,
>>>
>>> Pavel & Santiago
>>>
>>
>