├── .github
├── mergify.yml
├── workflows
│ ├── release-drafter.yml
│ └── build-test.yml
└── dependabot.yml
├── .gitignore
├── netty-reactive-streams-http
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── playframework
│ │ │ └── netty
│ │ │ └── http
│ │ │ ├── StreamedHttpRequest.java
│ │ │ ├── StreamedHttpResponse.java
│ │ │ ├── StreamedHttpMessage.java
│ │ │ ├── DelegateStreamedHttpRequest.java
│ │ │ ├── DelegateStreamedHttpResponse.java
│ │ │ ├── WebSocketHttpResponse.java
│ │ │ ├── DelegateHttpResponse.java
│ │ │ ├── DefaultStreamedHttpResponse.java
│ │ │ ├── DefaultStreamedHttpRequest.java
│ │ │ ├── DelegateHttpRequest.java
│ │ │ ├── DelegateHttpMessage.java
│ │ │ ├── DefaultWebSocketHttpResponse.java
│ │ │ ├── EmptyHttpResponse.java
│ │ │ ├── EmptyHttpRequest.java
│ │ │ ├── HttpStreamsClientHandler.java
│ │ │ ├── HttpStreamsServerHandler.java
│ │ │ └── HttpStreamsHandler.java
│ └── test
│ │ └── java
│ │ └── org
│ │ └── playframework
│ │ └── netty
│ │ └── http
│ │ ├── DelegateProcessor.java
│ │ ├── PekkoStreamsUtil.java
│ │ ├── ProcessorHttpServer.java
│ │ ├── ProcessorHttpClient.java
│ │ ├── HttpHelper.java
│ │ ├── FullStackHttpIdentityProcessorVerificationTest.java
│ │ ├── WebSocketsTest.java
│ │ └── HttpStreamsTest.java
└── pom.xml
├── netty-reactive-streams
├── src
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── playframework
│ │ │ └── netty
│ │ │ ├── probe
│ │ │ ├── PublisherProbe.java
│ │ │ ├── Probe.java
│ │ │ └── SubscriberProbe.java
│ │ │ ├── ScheduledBatchedProducer.java
│ │ │ ├── BatchedProducer.java
│ │ │ ├── HandlerSubscriberBlackboxVerificationTest.java
│ │ │ ├── ClosedLoopChannel.java
│ │ │ ├── ProbeHandler.java
│ │ │ ├── HandlerSubscriberWhiteboxVerificationTest.java
│ │ │ ├── ChannelPublisherTest.java
│ │ │ └── HandlerPublisherVerificationTest.java
│ └── main
│ │ └── java
│ │ └── org
│ │ └── playframework
│ │ └── netty
│ │ ├── CancelledSubscriber.java
│ │ ├── HandlerSubscriber.java
│ │ └── HandlerPublisher.java
└── pom.xml
├── README.md
├── pom.xml
└── LICENSE
/.github/mergify.yml:
--------------------------------------------------------------------------------
1 | extends: .github
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
3 | *.iml
4 | .idea/
5 | .project
6 | .settings/
7 | netty-reactive-streams-http/.classpath
8 | netty-reactive-streams-http/.project
9 | netty-reactive-streams-http/.settings/
10 | netty-reactive-streams/.classpath
11 | netty-reactive-streams/.project
12 | netty-reactive-streams/.settings/
13 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/StreamedHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpRequest;
4 |
5 | /**
6 | * Combines {@link HttpRequest} and {@link StreamedHttpMessage} into one
7 | * message. So it represents an http request with a stream of
8 | * {@link io.netty.handler.codec.http.HttpContent} messages that can be subscribed to.
9 | */
10 | public interface StreamedHttpRequest extends HttpRequest, StreamedHttpMessage {
11 | }
12 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/StreamedHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpResponse;
4 |
5 | /**
6 | * Combines {@link HttpResponse} and {@link StreamedHttpMessage} into one
7 | * message. So it represents an http response with a stream of
8 | * {@link io.netty.handler.codec.http.HttpContent} messages that can be subscribed to.
9 | */
10 | public interface StreamedHttpResponse extends HttpResponse, StreamedHttpMessage {
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | update_release_draft:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: release-drafter/release-drafter@v6
13 | with:
14 | name: "Netty Reactive Streams $RESOLVED_VERSION"
15 | config-name: release-drafts/increasing-minor-version.yml # located in .github/ in the default branch within this or the .github repo
16 | commitish: ${{ github.ref_name }}
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | push:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 | strategy:
14 | matrix:
15 | java: [ '8', '11', '17', '21' ]
16 | os: [ 'ubuntu-latest' ]
17 | runs-on: ${{ matrix.os }}
18 | steps:
19 | - uses: actions/checkout@v6
20 | - name: Set up JDK
21 | uses: actions/setup-java@v5
22 | with:
23 | java-version: ${{ matrix.java }}
24 | distribution: 'temurin'
25 | cache: 'maven'
26 | - name: Build
27 | run: mvn --no-transfer-progress -B clean package
28 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/StreamedHttpMessage.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpContent;
4 | import io.netty.handler.codec.http.HttpMessage;
5 | import org.reactivestreams.Publisher;
6 |
7 | /**
8 | * Combines {@link HttpMessage} and {@link Publisher} into one
9 | * message. So it represents an http message with a stream of {@link HttpContent}
10 | * messages that can be subscribed to.
11 | *
12 | * Note that receivers of this message must consume the publisher,
13 | * since the publisher will exert back pressure up the stream if not consumed.
14 | */
15 | public interface StreamedHttpMessage extends HttpMessage, Publisher {
16 | }
17 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DelegateStreamedHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpContent;
4 | import io.netty.handler.codec.http.HttpRequest;
5 | import org.reactivestreams.Publisher;
6 | import org.reactivestreams.Subscriber;
7 |
8 | final class DelegateStreamedHttpRequest extends DelegateHttpRequest implements StreamedHttpRequest {
9 |
10 | private final Publisher stream;
11 |
12 | public DelegateStreamedHttpRequest(HttpRequest request, Publisher stream) {
13 | super(request);
14 | this.stream = stream;
15 | }
16 |
17 | @Override
18 | public void subscribe(Subscriber super HttpContent> subscriber) {
19 | stream.subscribe(subscriber);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/probe/PublisherProbe.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.probe;
2 |
3 | import org.reactivestreams.Publisher;
4 | import org.reactivestreams.Subscriber;
5 |
6 | public class PublisherProbe extends Probe implements Publisher {
7 |
8 | private final Publisher publisher;
9 |
10 | public PublisherProbe(Publisher publisher, String name) {
11 | super(name);
12 | this.publisher = publisher;
13 | }
14 |
15 | @Override
16 | public void subscribe(Subscriber super T> s) {
17 | String sName = s == null ? "null" : s.getClass().getName();
18 | log("invoke subscribe with subscriber " + sName);
19 | publisher.subscribe(new SubscriberProbe<>(s, name, start));
20 | log("finish subscribe");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DelegateStreamedHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpContent;
4 | import io.netty.handler.codec.http.HttpResponse;
5 | import org.reactivestreams.Publisher;
6 | import org.reactivestreams.Subscriber;
7 |
8 | final class DelegateStreamedHttpResponse extends DelegateHttpResponse implements StreamedHttpResponse {
9 |
10 | private final Publisher stream;
11 |
12 | public DelegateStreamedHttpResponse(HttpResponse response, Publisher stream) {
13 | super(response);
14 | this.stream = stream;
15 | }
16 |
17 | @Override
18 | public void subscribe(Subscriber super HttpContent> subscriber) {
19 | stream.subscribe(subscriber);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/probe/Probe.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.probe;
2 |
3 | import java.util.Date;
4 |
5 | public class Probe {
6 |
7 | protected final String name;
8 | protected final Long start;
9 |
10 | /**
11 | * Create a new probe and log that it started.
12 | */
13 | protected Probe(String name) {
14 | this.name = name;
15 | start = System.nanoTime();
16 | log("Probe created at " + new Date());
17 | }
18 |
19 | /**
20 | * Create a new probe with the start time from another probe.
21 | */
22 | protected Probe(String name, long start) {
23 | this.name = name;
24 | this.start = start;
25 | }
26 |
27 | protected void log(String message) {
28 | System.out.println(String.format("%10d %-5s %-15s %s", (System.nanoTime() - start) / 1000, name, Thread.currentThread().getName(), message));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/main/java/org/playframework/netty/CancelledSubscriber.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import org.reactivestreams.Subscriber;
4 | import org.reactivestreams.Subscription;
5 |
6 | /**
7 | * A cancelled subscriber.
8 | */
9 | public final class CancelledSubscriber implements Subscriber {
10 |
11 | @Override
12 | public void onSubscribe(Subscription subscription) {
13 | if (subscription == null) {
14 | throw new NullPointerException("Null subscription");
15 | } else {
16 | subscription.cancel();
17 | }
18 | }
19 |
20 | @Override
21 | public void onNext(T t) {
22 | }
23 |
24 | @Override
25 | public void onError(Throwable error) {
26 | if (error == null) {
27 | throw new NullPointerException("Null error published");
28 | }
29 | }
30 |
31 | @Override
32 | public void onComplete() {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/WebSocketHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpResponse;
4 | import io.netty.handler.codec.http.websocketx.WebSocketFrame;
5 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
6 | import org.reactivestreams.Processor;
7 |
8 |
9 | /**
10 | * Combines {@link HttpResponse} and {@link Processor}
11 | * into one message. So it represents an http response with a processor that can handle
12 | * a WebSocket.
13 | *
14 | * This is only used for server side responses. For client side websocket requests, it's
15 | * better to configure the reactive streams pipeline directly.
16 | */
17 | public interface WebSocketHttpResponse extends HttpResponse, Processor {
18 | /**
19 | * Get the handshaker factory to use to reconfigure the channel.
20 | *
21 | * @return The handshaker factory.
22 | */
23 | WebSocketServerHandshakerFactory handshakerFactory();
24 | }
25 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DelegateHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpResponse;
4 | import io.netty.handler.codec.http.HttpResponseStatus;
5 | import io.netty.handler.codec.http.HttpVersion;
6 |
7 | class DelegateHttpResponse extends DelegateHttpMessage implements HttpResponse {
8 |
9 | protected final HttpResponse response;
10 |
11 | public DelegateHttpResponse(HttpResponse response) {
12 | super(response);
13 | this.response = response;
14 | }
15 |
16 | @Override
17 | public HttpResponse setStatus(HttpResponseStatus status) {
18 | response.setStatus(status);
19 | return this;
20 | }
21 |
22 | @Override
23 | @Deprecated
24 | public HttpResponseStatus getStatus() {
25 | return response.status();
26 | }
27 |
28 | @Override
29 | public HttpResponseStatus status() {
30 | return response.status();
31 | }
32 |
33 | @Override
34 | public HttpResponse setProtocolVersion(HttpVersion version) {
35 | super.setProtocolVersion(version);
36 | return this;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/test/java/org/playframework/netty/http/DelegateProcessor.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import org.reactivestreams.Processor;
4 | import org.reactivestreams.Publisher;
5 | import org.reactivestreams.Subscriber;
6 | import org.reactivestreams.Subscription;
7 |
8 | public class DelegateProcessor implements Processor {
9 |
10 | private final Subscriber subscriber;
11 | private final Publisher publisher;
12 |
13 | public DelegateProcessor(Subscriber subscriber, Publisher publisher) {
14 | this.subscriber = subscriber;
15 | this.publisher = publisher;
16 | }
17 |
18 | @Override
19 | public void subscribe(Subscriber super Out> s) {
20 | publisher.subscribe(s);
21 | }
22 |
23 | @Override
24 | public void onSubscribe(Subscription s) {
25 | subscriber.onSubscribe(s);
26 | }
27 |
28 | @Override
29 | public void onNext(In in) {
30 | subscriber.onNext(in);
31 | }
32 |
33 | @Override
34 | public void onError(Throwable t) {
35 | subscriber.onError(t);
36 | }
37 |
38 | @Override
39 | public void onComplete() {
40 | subscriber.onComplete();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DefaultStreamedHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.DefaultHttpResponse;
4 | import io.netty.handler.codec.http.HttpContent;
5 | import io.netty.handler.codec.http.HttpResponseStatus;
6 | import io.netty.handler.codec.http.HttpVersion;
7 | import org.reactivestreams.Publisher;
8 | import org.reactivestreams.Subscriber;
9 |
10 | /**
11 | * A default streamed HTTP response.
12 | */
13 | public class DefaultStreamedHttpResponse extends DefaultHttpResponse implements StreamedHttpResponse {
14 |
15 | private final Publisher stream;
16 |
17 | public DefaultStreamedHttpResponse(HttpVersion version, HttpResponseStatus status, Publisher stream) {
18 | super(version, status);
19 | this.stream = stream;
20 | }
21 |
22 | public DefaultStreamedHttpResponse(HttpVersion version, HttpResponseStatus status, boolean validateHeaders, Publisher stream) {
23 | super(version, status, validateHeaders);
24 | this.stream = stream;
25 | }
26 |
27 | @Override
28 | public void subscribe(Subscriber super HttpContent> subscriber) {
29 | stream.subscribe(subscriber);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DefaultStreamedHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.DefaultHttpRequest;
4 | import io.netty.handler.codec.http.HttpContent;
5 | import io.netty.handler.codec.http.HttpMethod;
6 | import io.netty.handler.codec.http.HttpVersion;
7 | import org.reactivestreams.Publisher;
8 | import org.reactivestreams.Subscriber;
9 |
10 | /**
11 | * A default streamed HTTP request.
12 | */
13 | public class DefaultStreamedHttpRequest extends DefaultHttpRequest implements StreamedHttpRequest{
14 |
15 | private final Publisher stream;
16 |
17 | public DefaultStreamedHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, Publisher stream) {
18 | super(httpVersion, method, uri);
19 | this.stream = stream;
20 | }
21 |
22 | public DefaultStreamedHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, boolean validateHeaders, Publisher stream) {
23 | super(httpVersion, method, uri, validateHeaders);
24 | this.stream = stream;
25 | }
26 |
27 | @Override
28 | public void subscribe(Subscriber super HttpContent> subscriber) {
29 | stream.subscribe(subscriber);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/test/java/org/playframework/netty/http/PekkoStreamsUtil.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import org.apache.pekko.japi.Pair;
4 | import org.apache.pekko.japi.function.Creator;
5 | import org.apache.pekko.stream.Materializer;
6 | import org.apache.pekko.stream.javadsl.*;
7 | import org.reactivestreams.Processor;
8 | import org.reactivestreams.Publisher;
9 | import org.reactivestreams.Subscriber;
10 |
11 | public class PekkoStreamsUtil {
12 |
13 | public static Processor flowToProcessor(Flow flow, Materializer materializer) {
14 | Pair, Publisher> pair =
15 | Source.asSubscriber()
16 | .via(flow)
17 | .toMat(Sink.asPublisher(AsPublisher.WITH_FANOUT), Keep., Publisher>both())
18 | .run(materializer);
19 |
20 | return new DelegateProcessor<>(pair.first(), pair.second());
21 | }
22 |
23 | public static Flow processorToFlow(final Processor processor) {
24 | return Flow.fromProcessor(new Creator>() {
25 | @Override
26 | public Processor create() throws Exception {
27 | return processor;
28 | }
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DelegateHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.HttpMethod;
4 | import io.netty.handler.codec.http.HttpRequest;
5 | import io.netty.handler.codec.http.HttpVersion;
6 |
7 | class DelegateHttpRequest extends DelegateHttpMessage implements HttpRequest {
8 |
9 | protected final HttpRequest request;
10 |
11 | public DelegateHttpRequest(HttpRequest request) {
12 | super(request);
13 | this.request = request;
14 | }
15 |
16 | @Override
17 | public HttpRequest setMethod(HttpMethod method) {
18 | request.setMethod(method);
19 | return this;
20 | }
21 |
22 | @Override
23 | public HttpRequest setUri(String uri) {
24 | request.setUri(uri);
25 | return this;
26 | }
27 |
28 | @Override
29 | @Deprecated
30 | public HttpMethod getMethod() {
31 | return request.method();
32 | }
33 |
34 | @Override
35 | public HttpMethod method() {
36 | return request.method();
37 | }
38 |
39 | @Override
40 | @Deprecated
41 | public String getUri() {
42 | return request.uri();
43 | }
44 |
45 | @Override
46 | public String uri() {
47 | return request.uri();
48 | }
49 |
50 | @Override
51 | public HttpRequest setProtocolVersion(HttpVersion version) {
52 | super.setProtocolVersion(version);
53 | return this;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DelegateHttpMessage.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.DecoderResult;
4 | import io.netty.handler.codec.http.*;
5 |
6 | class DelegateHttpMessage implements HttpMessage {
7 | protected final HttpMessage message;
8 |
9 | public DelegateHttpMessage(HttpMessage message) {
10 | this.message = message;
11 | }
12 |
13 | @Override
14 | @Deprecated
15 | public HttpVersion getProtocolVersion() {
16 | return message.protocolVersion();
17 | }
18 |
19 | @Override
20 | public HttpVersion protocolVersion() {
21 | return message.protocolVersion();
22 | }
23 |
24 | @Override
25 | public HttpMessage setProtocolVersion(HttpVersion version) {
26 | message.setProtocolVersion(version);
27 | return this;
28 | }
29 |
30 | @Override
31 | public HttpHeaders headers() {
32 | return message.headers();
33 | }
34 |
35 | @Override
36 | @Deprecated
37 | public DecoderResult getDecoderResult() {
38 | return message.decoderResult();
39 | }
40 |
41 | @Override
42 | public DecoderResult decoderResult() {
43 | return message.decoderResult();
44 | }
45 |
46 | @Override
47 | public void setDecoderResult(DecoderResult result) {
48 | message.setDecoderResult(result);
49 | }
50 |
51 | @Override
52 | public String toString() {
53 | return this.getClass().getName() + "(" + message.toString() + ")";
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/ScheduledBatchedProducer.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 |
5 | import java.util.concurrent.ScheduledExecutorService;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | /**
9 | * A batched producer.
10 | *
11 | * Responds to read requests with batches of elements according to batch size. When eofOn is reached, it closes the
12 | * channel.
13 | */
14 | public class ScheduledBatchedProducer extends BatchedProducer {
15 |
16 | private final ScheduledExecutorService executor;
17 | private final long delay;
18 |
19 | public ScheduledBatchedProducer(long eofOn, int batchSize, long sequence, ScheduledExecutorService executor, long delay) {
20 | super(eofOn, batchSize, sequence);
21 | this.executor = executor;
22 | this.delay = delay;
23 | }
24 |
25 | protected boolean complete;
26 |
27 | @Override
28 | public void read(final ChannelHandlerContext ctx) throws Exception {
29 | executor.schedule(new Runnable() {
30 | @Override
31 | public void run() {
32 | for (int i = 0; i < batchSize && sequence.get() != eofOn; i++) {
33 | ctx.fireChannelRead(sequence.getAndIncrement());
34 | }
35 | complete = eofOn == sequence.get();
36 | executor.schedule(new Runnable() {
37 | @Override
38 | public void run() {
39 | if (complete) {
40 | ctx.fireChannelInactive();
41 | } else {
42 | ctx.fireChannelReadComplete();
43 | }
44 | }
45 | }, delay, TimeUnit.MILLISECONDS);
46 | }
47 | }, delay, TimeUnit.MILLISECONDS);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/BatchedProducer.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import io.netty.channel.ChannelOutboundHandlerAdapter;
5 | import io.netty.channel.ChannelPromise;
6 |
7 | import java.util.concurrent.atomic.AtomicLong;
8 |
9 | /**
10 | * A batched producer.
11 | *
12 | * Responds to read requests with batches of elements according to batch size. When eofOn is reached, it closes the
13 | * channel.
14 | */
15 | public class BatchedProducer extends ChannelOutboundHandlerAdapter {
16 |
17 | protected final long eofOn;
18 | protected final int batchSize;
19 | protected final AtomicLong sequence;
20 |
21 | public BatchedProducer(long eofOn, int batchSize, long sequence) {
22 | this.eofOn = eofOn;
23 | this.batchSize = batchSize;
24 | this.sequence = new AtomicLong(sequence);
25 | }
26 |
27 | private boolean cancelled = false;
28 |
29 |
30 | @Override
31 | public void read(final ChannelHandlerContext ctx) throws Exception {
32 | if (cancelled) {
33 | throw new IllegalStateException("Received demand after being cancelled");
34 | }
35 | ctx.pipeline().channel().eventLoop().parent().execute(new Runnable() {
36 | @Override
37 | public void run() {
38 | for (int i = 0; i < batchSize && sequence.get() != eofOn; i++) {
39 | ctx.fireChannelRead(sequence.getAndIncrement());
40 | }
41 | if (eofOn == sequence.get()) {
42 | ctx.fireChannelInactive();
43 | } else {
44 | ctx.fireChannelReadComplete();
45 | }
46 | }
47 | });
48 | }
49 |
50 | @Override
51 | public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
52 | if (cancelled) {
53 | throw new IllegalStateException("Cancelled twice");
54 | }
55 | cancelled = true;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/probe/SubscriberProbe.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.probe;
2 |
3 | import org.reactivestreams.Subscriber;
4 | import org.reactivestreams.Subscription;
5 |
6 | public class SubscriberProbe extends Probe implements Subscriber {
7 |
8 | private final Subscriber subscriber;
9 |
10 | public SubscriberProbe(Subscriber subscriber, String name) {
11 | super(name);
12 | this.subscriber = subscriber;
13 | }
14 |
15 | SubscriberProbe(Subscriber subscriber, String name, long start) {
16 | super(name, start);
17 | this.subscriber = subscriber;
18 | }
19 |
20 | @Override
21 | public void onSubscribe(final Subscription s) {
22 | String sName = s == null ? "null" : s.getClass().getName();
23 | log("invoke onSubscribe with subscription " + sName);
24 | subscriber.onSubscribe(new Subscription() {
25 | @Override
26 | public void request(long n) {
27 | log("invoke request " + n);
28 | s.request(n);
29 | log("finish request");
30 | }
31 |
32 | @Override
33 | public void cancel() {
34 | log("invoke cancel");
35 | s.cancel();
36 | log("finish cancel");
37 | }
38 | });
39 | log("finish onSubscribe");
40 | }
41 |
42 | @Override
43 | public void onNext(T t) {
44 | log("invoke onNext with message " + t);
45 | subscriber.onNext(t);
46 | log("finish onNext");
47 | }
48 |
49 | @Override
50 | public void onError(Throwable t) {
51 | String tName = t == null ? "null" : t.getClass().getName();
52 | log("invoke onError with " + tName);
53 | subscriber.onError(t);
54 | log("finish onError");
55 | }
56 |
57 | @Override
58 | public void onComplete() {
59 | log("invoke onComplete");
60 | subscriber.onComplete();
61 | log("finish onComplete");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: "maven"
8 | directory: "/netty-reactive-streams"
9 | schedule:
10 | interval: "weekly"
11 | - package-ecosystem: "maven"
12 | directory: "/netty-reactive-streams-http"
13 | schedule:
14 | interval: "weekly"
15 | - package-ecosystem: "maven"
16 | directory: "/"
17 | schedule:
18 | interval: "weekly"
19 | target-branch: "2.0.x"
20 | commit-message:
21 | prefix: "[2.0.x] "
22 | - package-ecosystem: "maven"
23 | directory: "/netty-reactive-streams"
24 | schedule:
25 | interval: "weekly"
26 | target-branch: "2.0.x"
27 | commit-message:
28 | prefix: "[2.0.x] "
29 | - package-ecosystem: "maven"
30 | directory: "/netty-reactive-streams-http"
31 | schedule:
32 | interval: "weekly"
33 | target-branch: "2.0.x"
34 | commit-message:
35 | prefix: "[2.0.x] "
36 | - package-ecosystem: "maven"
37 | directory: "/"
38 | schedule:
39 | interval: "weekly"
40 | target-branch: "3.0.x"
41 | commit-message:
42 | prefix: "[3.0.x] "
43 | - package-ecosystem: "maven"
44 | directory: "/netty-reactive-streams"
45 | schedule:
46 | interval: "weekly"
47 | target-branch: "3.0.x"
48 | commit-message:
49 | prefix: "[3.0.x] "
50 | - package-ecosystem: "maven"
51 | directory: "/netty-reactive-streams-http"
52 | schedule:
53 | interval: "weekly"
54 | target-branch: "3.0.x"
55 | commit-message:
56 | prefix: "[3.0.x] "
57 | - package-ecosystem: "github-actions"
58 | directory: "/"
59 | schedule:
60 | interval: "weekly"
61 | - package-ecosystem: "github-actions"
62 | directory: "/"
63 | schedule:
64 | interval: "weekly"
65 | target-branch: "2.0.x"
66 | commit-message:
67 | prefix: "[2.0.x] "
68 | - package-ecosystem: "github-actions"
69 | directory: "/"
70 | schedule:
71 | interval: "weekly"
72 | target-branch: "3.0.x"
73 | commit-message:
74 | prefix: "[3.0.x] "
75 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/DefaultWebSocketHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.handler.codec.http.DefaultHttpResponse;
4 | import io.netty.handler.codec.http.HttpResponseStatus;
5 | import io.netty.handler.codec.http.HttpVersion;
6 | import io.netty.handler.codec.http.websocketx.WebSocketFrame;
7 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
8 | import org.reactivestreams.Processor;
9 | import org.reactivestreams.Subscriber;
10 | import org.reactivestreams.Subscription;
11 |
12 | /**
13 | * A default WebSocket HTTP response.
14 | */
15 | public class DefaultWebSocketHttpResponse extends DefaultHttpResponse implements WebSocketHttpResponse {
16 |
17 | private final Processor processor;
18 | private final WebSocketServerHandshakerFactory handshakerFactory;
19 |
20 | public DefaultWebSocketHttpResponse(HttpVersion version, HttpResponseStatus status,
21 | Processor processor,
22 | WebSocketServerHandshakerFactory handshakerFactory) {
23 | super(version, status);
24 | this.processor = processor;
25 | this.handshakerFactory = handshakerFactory;
26 | }
27 |
28 | public DefaultWebSocketHttpResponse(HttpVersion version, HttpResponseStatus status, boolean validateHeaders,
29 | Processor processor,
30 | WebSocketServerHandshakerFactory handshakerFactory) {
31 | super(version, status, validateHeaders);
32 | this.processor = processor;
33 | this.handshakerFactory = handshakerFactory;
34 | }
35 |
36 | @Override
37 | public WebSocketServerHandshakerFactory handshakerFactory() {
38 | return handshakerFactory;
39 | }
40 |
41 | @Override
42 | public void subscribe(Subscriber super WebSocketFrame> subscriber) {
43 | processor.subscribe(subscriber);
44 | }
45 |
46 | @Override
47 | public void onSubscribe(Subscription subscription) {
48 | processor.onSubscribe(subscription);
49 | }
50 |
51 | @Override
52 | public void onNext(WebSocketFrame webSocketFrame) {
53 | processor.onNext(webSocketFrame);
54 | }
55 |
56 | @Override
57 | public void onError(Throwable error) {
58 | processor.onError(error);
59 | }
60 |
61 | @Override
62 | public void onComplete() {
63 | processor.onComplete();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/test/java/org/playframework/netty/http/ProcessorHttpServer.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import org.playframework.netty.HandlerPublisher;
4 | import org.playframework.netty.HandlerSubscriber;
5 | import io.netty.bootstrap.ServerBootstrap;
6 | import io.netty.channel.*;
7 | import io.netty.channel.socket.SocketChannel;
8 | import io.netty.channel.socket.nio.NioServerSocketChannel;
9 | import io.netty.handler.codec.http.HttpRequest;
10 | import io.netty.handler.codec.http.HttpRequestDecoder;
11 | import io.netty.handler.codec.http.HttpResponse;
12 | import io.netty.handler.codec.http.HttpResponseEncoder;
13 | import org.reactivestreams.Processor;
14 |
15 | import java.net.SocketAddress;
16 | import java.util.concurrent.Callable;
17 |
18 | public class ProcessorHttpServer {
19 |
20 | private final EventLoopGroup eventLoop;
21 |
22 | public ProcessorHttpServer(EventLoopGroup eventLoop) {
23 | this.eventLoop = eventLoop;
24 | }
25 |
26 | public ChannelFuture bind(SocketAddress address, final Callable> handler) {
27 | ServerBootstrap bootstrap = new ServerBootstrap();
28 | bootstrap.group(eventLoop)
29 | .channel(NioServerSocketChannel.class)
30 | .childOption(ChannelOption.AUTO_READ, false)
31 | .localAddress(address)
32 | .childHandler(new ChannelInitializer() {
33 | @Override
34 | protected void initChannel(SocketChannel ch) throws Exception {
35 | ChannelPipeline pipeline = ch.pipeline();
36 |
37 | pipeline.addLast(
38 | new HttpRequestDecoder(),
39 | new HttpResponseEncoder()
40 | ).addLast("serverStreamsHandler", new HttpStreamsServerHandler());
41 |
42 | HandlerSubscriber subscriber = new HandlerSubscriber<>(ch.eventLoop(), 2, 4);
43 | HandlerPublisher publisher = new HandlerPublisher<>(ch.eventLoop(), HttpRequest.class);
44 |
45 | pipeline.addLast("serverSubscriber", subscriber);
46 | pipeline.addLast("serverPublisher", publisher);
47 |
48 | Processor processor = handler.call();
49 | processor.subscribe(subscriber);
50 | publisher.subscribe(processor);
51 | }
52 | });
53 |
54 | return bootstrap.bind();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/HandlerSubscriberBlackboxVerificationTest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.ChannelDuplexHandler;
4 | import io.netty.channel.ChannelHandler;
5 | import io.netty.channel.embedded.EmbeddedChannel;
6 | import org.reactivestreams.Subscriber;
7 | import org.reactivestreams.Subscription;
8 | import org.reactivestreams.tck.SubscriberBlackboxVerification;
9 | import org.reactivestreams.tck.TestEnvironment;
10 |
11 | public class HandlerSubscriberBlackboxVerificationTest extends SubscriberBlackboxVerification {
12 |
13 | public HandlerSubscriberBlackboxVerificationTest() {
14 | super(new TestEnvironment());
15 | }
16 |
17 | @Override
18 | public Subscriber createSubscriber() {
19 | // Embedded channel requires at least one handler when it's created, but HandlerSubscriber
20 | // needs the channels event loop in order to be created, so start with a dummy, then replace.
21 | ChannelHandler dummy = new ChannelDuplexHandler();
22 | EmbeddedChannel channel = new EmbeddedChannel(dummy);
23 | HandlerSubscriber subscriber = new HandlerSubscriber<>(channel.eventLoop(), 2, 4);
24 | channel.pipeline().replace(dummy, "subscriber", subscriber);
25 |
26 | return new SubscriberWithChannel<>(channel, subscriber);
27 | }
28 |
29 | @Override
30 | public Long createElement(int element) {
31 | return (long) element;
32 | }
33 |
34 | @Override
35 | public void triggerRequest(Subscriber super Long> subscriber) {
36 | EmbeddedChannel channel = ((SubscriberWithChannel) subscriber).channel;
37 |
38 | channel.runPendingTasks();
39 | while (channel.readOutbound() != null) {
40 | channel.runPendingTasks();
41 | }
42 | channel.runPendingTasks();
43 | }
44 |
45 | /**
46 | * Delegate subscriber that makes the embedded channel available so we can talk to it to trigger a request.
47 | */
48 | private static class SubscriberWithChannel implements Subscriber {
49 | final EmbeddedChannel channel;
50 | final HandlerSubscriber subscriber;
51 |
52 | public SubscriberWithChannel(EmbeddedChannel channel, HandlerSubscriber subscriber) {
53 | this.channel = channel;
54 | this.subscriber = subscriber;
55 | }
56 |
57 | public void onSubscribe(Subscription s) {
58 | subscriber.onSubscribe(s);
59 | }
60 |
61 | public void onNext(T t) {
62 | subscriber.onNext(t);
63 | }
64 |
65 | public void onError(Throwable t) {
66 | subscriber.onError(t);
67 | }
68 |
69 | public void onComplete() {
70 | subscriber.onComplete();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/test/java/org/playframework/netty/http/ProcessorHttpClient.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import org.playframework.netty.HandlerPublisher;
4 | import org.playframework.netty.HandlerSubscriber;
5 | import io.netty.bootstrap.Bootstrap;
6 | import io.netty.channel.*;
7 | import io.netty.channel.socket.SocketChannel;
8 | import io.netty.channel.socket.nio.NioSocketChannel;
9 | import io.netty.handler.codec.http.*;
10 | import io.netty.util.concurrent.*;
11 | import org.reactivestreams.Processor;
12 |
13 | import java.net.SocketAddress;
14 |
15 | public class ProcessorHttpClient {
16 |
17 | private final EventLoopGroup eventLoop;
18 |
19 | public ProcessorHttpClient(EventLoopGroup eventLoop) {
20 | this.eventLoop = eventLoop;
21 | }
22 |
23 | public Processor connect(SocketAddress address) {
24 |
25 | final EventExecutor executor = eventLoop.next();
26 | final Promise promise = new DefaultPromise<>(executor);
27 | final HandlerPublisher publisherHandler = new HandlerPublisher<>(executor,
28 | HttpResponse.class);
29 | final HandlerSubscriber subscriberHandler = new HandlerSubscriber(
30 | executor, 2, 4) {
31 | @Override
32 | protected void error(final Throwable error) {
33 |
34 | promise.addListener(new GenericFutureListener>() {
35 | @Override
36 | public void operationComplete(Future future) throws Exception {
37 | ChannelPipeline pipeline = future.getNow();
38 | pipeline.fireExceptionCaught(error);
39 | pipeline.close();
40 | }
41 | });
42 | }
43 | };
44 |
45 | Bootstrap client = new Bootstrap()
46 | .group(eventLoop)
47 | .option(ChannelOption.AUTO_READ, false)
48 | .channel(NioSocketChannel.class)
49 | .handler(new ChannelInitializer() {
50 | @Override
51 | protected void initChannel(SocketChannel ch) throws Exception {
52 |
53 | final ChannelPipeline pipeline = ch.pipeline();
54 |
55 |
56 | pipeline
57 | .addLast(new HttpClientCodec())
58 | .addLast("clientStreamsHandler", new HttpStreamsClientHandler())
59 | .addLast(executor, "clientPublisher", publisherHandler)
60 | .addLast(executor, "clientSubscriber", subscriberHandler);
61 |
62 | promise.setSuccess(pipeline);
63 | }
64 | });
65 |
66 | client.remoteAddress(address).connect();
67 | return new DelegateProcessor<>(subscriberHandler, publisherHandler);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/ClosedLoopChannel.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.*;
4 |
5 | import java.net.SocketAddress;
6 |
7 | /**
8 | * A closed loop channel that sends no events and receives no events, for testing purposes.
9 | *
10 | * Any outgoing events that reach the channel will throw an exception. All events should be caught
11 | * be inserting a handler that catches them and responds accordingly.
12 | */
13 | public class ClosedLoopChannel extends AbstractChannel {
14 |
15 | private final ChannelConfig config = new DefaultChannelConfig(this);
16 | private static final ChannelMetadata metadata = new ChannelMetadata(false);
17 |
18 | private volatile boolean open = true;
19 | private volatile boolean active = true;
20 |
21 | public ClosedLoopChannel() {
22 | super(null);
23 | }
24 |
25 | public void setOpen(boolean open) {
26 | this.open = open;
27 | }
28 |
29 | public void setActive(boolean active) {
30 | this.active = active;
31 | }
32 |
33 | @Override
34 | protected AbstractUnsafe newUnsafe() {
35 | return new AbstractUnsafe() {
36 | @Override
37 | public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
38 | throw new UnsupportedOperationException();
39 | }
40 | };
41 | }
42 |
43 | @Override
44 | protected boolean isCompatible(EventLoop loop) {
45 | return true;
46 | }
47 |
48 | @Override
49 | protected SocketAddress localAddress0() {
50 | throw new UnsupportedOperationException();
51 | }
52 |
53 | @Override
54 | protected SocketAddress remoteAddress0() {
55 | throw new UnsupportedOperationException();
56 | }
57 |
58 | @Override
59 | protected void doBind(SocketAddress localAddress) throws Exception {
60 | throw new UnsupportedOperationException();
61 | }
62 |
63 | @Override
64 | protected void doDisconnect() throws Exception {
65 | throw new UnsupportedOperationException();
66 | }
67 |
68 | @Override
69 | protected void doClose() throws Exception {
70 | this.open = false;
71 | }
72 |
73 | @Override
74 | protected void doBeginRead() throws Exception {
75 | throw new UnsupportedOperationException();
76 | }
77 |
78 | @Override
79 | protected void doWrite(ChannelOutboundBuffer in) throws Exception {
80 | throw new UnsupportedOperationException();
81 | }
82 |
83 | @Override
84 | public ChannelConfig config() {
85 | return config;
86 | }
87 |
88 | @Override
89 | public boolean isOpen() {
90 | return open;
91 | }
92 |
93 | @Override
94 | public boolean isActive() {
95 | return active;
96 | }
97 |
98 | @Override
99 | public ChannelMetadata metadata() {
100 | return metadata;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/netty-reactive-streams/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.playframework.netty
7 | netty-reactive-streams-parent
8 | 3.1.0-M2-SNAPSHOT
9 |
10 |
11 | netty-reactive-streams
12 |
13 | Netty Reactive Streams Implementation
14 | jar
15 |
16 |
17 |
18 | io.netty
19 | netty-handler
20 |
21 |
22 | org.reactivestreams
23 | reactive-streams
24 |
25 |
26 | org.reactivestreams
27 | reactive-streams-tck
28 |
29 |
30 | org.testng
31 | testng
32 |
33 |
34 |
35 |
36 |
37 |
38 | maven-resources-plugin
39 | 3.4.0
40 |
41 |
42 | copy-resources
43 | validate
44 |
45 | copy-resources
46 |
47 |
48 | ${project.build.resources[0].directory}/META-INF
49 |
50 |
51 | ../
52 | true
53 |
54 | README.md
55 | LICENSE
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-jar-plugin
66 |
67 |
68 | default-jar
69 | package
70 |
71 | jar
72 |
73 |
74 |
75 |
76 | org.playframework.netty.core
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/EmptyHttpResponse.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.handler.codec.http.*;
6 | import io.netty.util.ReferenceCountUtil;
7 | import io.netty.util.ReferenceCounted;
8 |
9 | class EmptyHttpResponse extends DelegateHttpResponse implements FullHttpResponse {
10 |
11 | public EmptyHttpResponse(HttpResponse response) {
12 | super(response);
13 | }
14 |
15 | @Override
16 | public FullHttpResponse setStatus(HttpResponseStatus status) {
17 | super.setStatus(status);
18 | return this;
19 | }
20 |
21 | @Override
22 | public FullHttpResponse setProtocolVersion(HttpVersion version) {
23 | super.setProtocolVersion(version);
24 | return this;
25 | }
26 |
27 | @Override
28 | public FullHttpResponse copy() {
29 | if (response instanceof FullHttpResponse) {
30 | return new EmptyHttpResponse(((FullHttpResponse) response).copy());
31 | } else {
32 | DefaultHttpResponse copy = new DefaultHttpResponse(protocolVersion(), status());
33 | copy.headers().set(headers());
34 | return new EmptyHttpResponse(copy);
35 | }
36 | }
37 |
38 | @Override
39 | public FullHttpResponse retain(int increment) {
40 | ReferenceCountUtil.retain(message, increment);
41 | return this;
42 | }
43 |
44 | @Override
45 | public FullHttpResponse retain() {
46 | ReferenceCountUtil.retain(message);
47 | return this;
48 | }
49 |
50 | @Override
51 | public FullHttpResponse touch() {
52 | if (response instanceof FullHttpResponse) {
53 | return ((FullHttpResponse) response).touch();
54 | } else {
55 | return this;
56 | }
57 | }
58 |
59 | @Override
60 | public FullHttpResponse touch(Object o) {
61 | if (response instanceof FullHttpResponse) {
62 | return ((FullHttpResponse) response).touch(o);
63 | } else {
64 | return this;
65 | }
66 | }
67 |
68 | @Override
69 | public HttpHeaders trailingHeaders() {
70 | return new DefaultHttpHeaders();
71 | }
72 |
73 | @Override
74 | public FullHttpResponse duplicate() {
75 | if (response instanceof FullHttpResponse) {
76 | return ((FullHttpResponse) response).duplicate();
77 | } else {
78 | return this;
79 | }
80 | }
81 |
82 | @Override
83 | public FullHttpResponse retainedDuplicate() {
84 | if (response instanceof FullHttpResponse) {
85 | return ((FullHttpResponse) response).retainedDuplicate();
86 | } else {
87 | return this;
88 | }
89 | }
90 |
91 | @Override
92 | public FullHttpResponse replace(ByteBuf byteBuf) {
93 | if (response instanceof FullHttpResponse) {
94 | return ((FullHttpResponse) response).replace(byteBuf);
95 | } else {
96 | return this;
97 | }
98 | }
99 |
100 | @Override
101 | public ByteBuf content() {
102 | return Unpooled.EMPTY_BUFFER;
103 | }
104 |
105 | @Override
106 | public int refCnt() {
107 | if (message instanceof ReferenceCounted) {
108 | return ((ReferenceCounted) message).refCnt();
109 | } else {
110 | return 1;
111 | }
112 | }
113 |
114 | @Override
115 | public boolean release() {
116 | return ReferenceCountUtil.release(message);
117 | }
118 |
119 | @Override
120 | public boolean release(int decrement) {
121 | return ReferenceCountUtil.release(message, decrement);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/ProbeHandler.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.ChannelDuplexHandler;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelPromise;
6 | import org.reactivestreams.Subscriber;
7 | import org.reactivestreams.Subscription;
8 | import org.reactivestreams.tck.SubscriberWhiteboxVerification;
9 |
10 | import java.util.LinkedList;
11 | import java.util.Queue;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | public class ProbeHandler extends ChannelDuplexHandler implements SubscriberWhiteboxVerification.SubscriberPuppet {
15 |
16 | private static final int NO_CONTEXT = 0;
17 | private static final int RUN = 1;
18 | private static final int CANCEL = 2;
19 |
20 | private final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe probe;
21 | private final Class clazz;
22 | private final Queue queue = new LinkedList<>();
23 | private final AtomicInteger state = new AtomicInteger(NO_CONTEXT);
24 | private volatile ChannelHandlerContext ctx;
25 | // Netty doesn't provide a way to send errors out, so we capture whether it was an error or complete here
26 | private volatile Throwable receivedError = null;
27 |
28 | public ProbeHandler(SubscriberWhiteboxVerification.WhiteboxSubscriberProbe probe, Class clazz) {
29 | this.probe = probe;
30 | this.clazz = clazz;
31 | }
32 |
33 | @Override
34 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
35 | this.ctx = ctx;
36 | if (!state.compareAndSet(NO_CONTEXT, RUN)) {
37 | ctx.fireChannelInactive();
38 | }
39 | }
40 |
41 | @Override
42 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
43 | queue.add(new WriteEvent(clazz.cast(msg), promise));
44 | }
45 |
46 | @Override
47 | public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception {
48 | if (receivedError == null) {
49 | probe.registerOnComplete();
50 | } else {
51 | probe.registerOnError(receivedError);
52 | }
53 | }
54 |
55 | @Override
56 | public void flush(ChannelHandlerContext ctx) throws Exception {
57 | while (!queue.isEmpty()) {
58 | WriteEvent event = queue.remove();
59 | probe.registerOnNext(event.msg);
60 | event.future.setSuccess();
61 | }
62 | }
63 |
64 | @Override
65 | public void triggerRequest(long elements) {
66 | // No need, the channel automatically requests
67 | }
68 |
69 | @Override
70 | public void signalCancel() {
71 | if (!state.compareAndSet(NO_CONTEXT, CANCEL)) {
72 | ctx.fireChannelInactive();
73 | }
74 | }
75 |
76 | private class WriteEvent {
77 | final T msg;
78 | final ChannelPromise future;
79 |
80 | private WriteEvent(T msg, ChannelPromise future) {
81 | this.msg = msg;
82 | this.future = future;
83 | }
84 | }
85 |
86 | public Subscriber wrap(final Subscriber subscriber) {
87 | return new Subscriber() {
88 | public void onSubscribe(Subscription s) {
89 | probe.registerOnSubscribe(ProbeHandler.this);
90 | subscriber.onSubscribe(s);
91 | }
92 | public void onNext(T t) {
93 | subscriber.onNext(t);
94 | }
95 | public void onError(Throwable t) {
96 | receivedError = t;
97 | subscriber.onError(t);
98 | }
99 | public void onComplete() {
100 | subscriber.onComplete();
101 | }
102 | };
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.playframework.netty
7 | netty-reactive-streams-parent
8 | 3.1.0-M2-SNAPSHOT
9 |
10 |
11 | netty-reactive-streams-http
12 |
13 | Netty Reactive Streams HTTP support
14 | jar
15 |
16 |
17 |
18 | ${project.groupId}
19 | netty-reactive-streams
20 | ${project.version}
21 |
22 |
23 | io.netty
24 | netty-codec-http
25 |
26 |
27 | org.reactivestreams
28 | reactive-streams-tck
29 |
30 |
31 | org.testng
32 | testng
33 |
34 |
35 | org.apache.pekko
36 | pekko-stream_2.12
37 |
38 |
39 |
40 |
41 |
42 |
43 | maven-resources-plugin
44 | 3.4.0
45 |
46 |
47 | copy-resources
48 | validate
49 |
50 | copy-resources
51 |
52 |
53 | ${project.build.resources[0].directory}/META-INF
54 |
55 |
56 | ../
57 | true
58 |
59 | README.md
60 | LICENSE
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | org.apache.maven.plugins
70 | maven-jar-plugin
71 |
72 |
73 | default-jar
74 | package
75 |
76 | jar
77 |
78 |
79 |
80 |
81 | org.playframework.netty.http
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/EmptyHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.handler.codec.http.*;
6 | import io.netty.util.ReferenceCountUtil;
7 | import io.netty.util.ReferenceCounted;
8 |
9 | class EmptyHttpRequest extends DelegateHttpRequest implements FullHttpRequest {
10 |
11 | public EmptyHttpRequest(HttpRequest request) {
12 | super(request);
13 | }
14 |
15 | @Override
16 | public FullHttpRequest setUri(String uri) {
17 | super.setUri(uri);
18 | return this;
19 | }
20 |
21 | @Override
22 | public FullHttpRequest setMethod(HttpMethod method) {
23 | super.setMethod(method);
24 | return this;
25 | }
26 |
27 | @Override
28 | public FullHttpRequest setProtocolVersion(HttpVersion version) {
29 | super.setProtocolVersion(version);
30 | return this;
31 | }
32 |
33 | @Override
34 | public FullHttpRequest copy() {
35 | if (request instanceof FullHttpRequest) {
36 | return new EmptyHttpRequest(((FullHttpRequest) request).copy());
37 | } else {
38 | DefaultHttpRequest copy = new DefaultHttpRequest(protocolVersion(), method(), uri());
39 | copy.headers().set(headers());
40 | return new EmptyHttpRequest(copy);
41 | }
42 | }
43 |
44 | @Override
45 | public FullHttpRequest retain(int increment) {
46 | ReferenceCountUtil.retain(message, increment);
47 | return this;
48 | }
49 |
50 | @Override
51 | public FullHttpRequest retain() {
52 | ReferenceCountUtil.retain(message);
53 | return this;
54 | }
55 |
56 | @Override
57 | public FullHttpRequest touch() {
58 | if (request instanceof FullHttpRequest) {
59 | return ((FullHttpRequest) request).touch();
60 | } else {
61 | return this;
62 | }
63 | }
64 |
65 | @Override
66 | public FullHttpRequest touch(Object o) {
67 | if (request instanceof FullHttpRequest) {
68 | return ((FullHttpRequest) request).touch(o);
69 | } else {
70 | return this;
71 | }
72 | }
73 |
74 | @Override
75 | public HttpHeaders trailingHeaders() {
76 | return new DefaultHttpHeaders();
77 | }
78 |
79 | @Override
80 | public FullHttpRequest duplicate() {
81 | if (request instanceof FullHttpRequest) {
82 | return ((FullHttpRequest) request).duplicate();
83 | } else {
84 | return this;
85 | }
86 | }
87 |
88 | @Override
89 | public FullHttpRequest retainedDuplicate() {
90 | if (request instanceof FullHttpRequest) {
91 | return ((FullHttpRequest) request).retainedDuplicate();
92 | } else {
93 | return this;
94 | }
95 | }
96 |
97 | @Override
98 | public FullHttpRequest replace(ByteBuf byteBuf) {
99 | if (message instanceof FullHttpRequest) {
100 | return ((FullHttpRequest) request).replace(byteBuf);
101 | } else {
102 | return this;
103 | }
104 | }
105 |
106 | @Override
107 | public ByteBuf content() {
108 | return Unpooled.EMPTY_BUFFER;
109 | }
110 |
111 | @Override
112 | public int refCnt() {
113 | if (message instanceof ReferenceCounted) {
114 | return ((ReferenceCounted) message).refCnt();
115 | } else {
116 | return 1;
117 | }
118 | }
119 |
120 | @Override
121 | public boolean release() {
122 | return ReferenceCountUtil.release(message);
123 | }
124 |
125 | @Override
126 | public boolean release(int decrement) {
127 | return ReferenceCountUtil.release(message, decrement);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/HandlerSubscriberWhiteboxVerificationTest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.*;
4 | import io.netty.util.concurrent.DefaultPromise;
5 | import io.netty.util.concurrent.Promise;
6 | import org.reactivestreams.Subscriber;
7 | import org.reactivestreams.tck.SubscriberWhiteboxVerification;
8 | import org.reactivestreams.tck.TestEnvironment;
9 | import org.testng.annotations.AfterMethod;
10 | import org.testng.annotations.BeforeMethod;
11 |
12 | public class HandlerSubscriberWhiteboxVerificationTest extends SubscriberWhiteboxVerification {
13 |
14 | private boolean workAroundIssue277;
15 |
16 | public HandlerSubscriberWhiteboxVerificationTest() {
17 | super(new TestEnvironment());
18 | }
19 |
20 | private DefaultEventLoopGroup eventLoop;
21 |
22 | // I tried making this before/after class, but encountered a strange error where after 32 publishers were created,
23 | // the following tests complained about the executor being shut down when I registered the channel. Though, it
24 | // doesn't happen if you create 32 publishers in a single test.
25 | @BeforeMethod
26 | public void startEventLoop() {
27 | workAroundIssue277 = false;
28 | eventLoop = new DefaultEventLoopGroup();
29 | }
30 |
31 | @AfterMethod
32 | public void stopEventLoop() {
33 | eventLoop.shutdownGracefully();
34 | eventLoop = null;
35 | }
36 |
37 | @Override
38 | public Subscriber createSubscriber(WhiteboxSubscriberProbe probe) {
39 |
40 |
41 | final ClosedLoopChannel channel = new ClosedLoopChannel();
42 | channel.config().setAutoRead(false);
43 | ChannelFuture registered = eventLoop.register(channel);
44 |
45 | final HandlerSubscriber subscriber = new HandlerSubscriber<>(registered.channel().eventLoop(), 2, 4);
46 | final ProbeHandler probeHandler = new ProbeHandler<>(probe, Long.class);
47 | final Promise handlersInPlace = new DefaultPromise<>(eventLoop.next());
48 |
49 | registered.addListener(new ChannelFutureListener() {
50 | @Override
51 | public void operationComplete(ChannelFuture future) throws Exception {
52 | channel.pipeline().addLast("probe", probeHandler);
53 | channel.pipeline().addLast("subscriber", subscriber);
54 | handlersInPlace.setSuccess(null);
55 | // Channel needs to be active before the subscriber starts responding to demand
56 | channel.pipeline().fireChannelActive();
57 | }
58 | });
59 |
60 | if (workAroundIssue277) {
61 | try {
62 | // Wait for the pipeline to be setup, so we're ready to receive elements even if they aren't requested,
63 | // because https://github.com/reactive-streams/reactive-streams-jvm/issues/277
64 | handlersInPlace.await();
65 | } catch (InterruptedException e) {
66 | throw new RuntimeException(e);
67 | }
68 | }
69 |
70 | return probeHandler.wrap(subscriber);
71 | }
72 |
73 | @Override
74 | public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable {
75 | // See https://github.com/reactive-streams/reactive-streams-jvm/issues/277
76 | workAroundIssue277 = true;
77 | super.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel();
78 | }
79 |
80 | @Override
81 | public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable {
82 | workAroundIssue277 = true;
83 | super.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced();
84 | }
85 |
86 | @Override
87 | public Long createElement(int element) {
88 | return (long) element;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/ChannelPublisherTest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.bootstrap.Bootstrap;
4 | import io.netty.channel.*;
5 | import io.netty.channel.nio.NioEventLoopGroup;
6 | import io.netty.channel.socket.nio.NioServerSocketChannel;
7 | import io.netty.util.concurrent.DefaultPromise;
8 | import io.netty.util.concurrent.Promise;
9 | import org.reactivestreams.Publisher;
10 | import org.reactivestreams.Subscriber;
11 | import org.reactivestreams.Subscription;
12 | import org.testng.annotations.AfterMethod;
13 | import org.testng.annotations.BeforeMethod;
14 | import org.testng.annotations.Test;
15 |
16 | import java.io.InputStream;
17 | import java.io.OutputStream;
18 | import java.net.InetSocketAddress;
19 | import java.net.Socket;
20 | import java.util.concurrent.BlockingQueue;
21 | import java.util.concurrent.LinkedBlockingQueue;
22 | import java.util.concurrent.TimeUnit;
23 |
24 | import static org.testng.Assert.assertEquals;
25 | import static org.testng.Assert.assertNotNull;
26 | import static org.testng.Assert.assertNull;
27 |
28 | public class ChannelPublisherTest {
29 |
30 | private EventLoopGroup group;
31 | private Channel channel;
32 | private Publisher publisher;
33 | private SubscriberProbe subscriber;
34 |
35 | @BeforeMethod
36 | public void start() throws Exception {
37 | group = new NioEventLoopGroup();
38 | EventLoop eventLoop = group.next();
39 |
40 | HandlerPublisher handlerPublisher = new HandlerPublisher<>(eventLoop, Channel.class);
41 | Bootstrap bootstrap = new Bootstrap();
42 |
43 | bootstrap
44 | .channel(NioServerSocketChannel.class)
45 | .group(eventLoop)
46 | .option(ChannelOption.AUTO_READ, false)
47 | .handler(handlerPublisher)
48 | .localAddress("127.0.0.1", 0);
49 |
50 | channel = bootstrap.bind().await().channel();
51 | this.publisher = handlerPublisher;
52 |
53 | subscriber = new SubscriberProbe<>();
54 | }
55 |
56 | @AfterMethod
57 | public void stop() throws Exception {
58 | channel.unsafe().closeForcibly();
59 | group.shutdownGracefully();
60 | }
61 |
62 | @Test
63 | public void test() throws Exception {
64 | publisher.subscribe(subscriber);
65 | Subscription sub = subscriber.takeSubscription();
66 |
67 | // Try one cycle
68 | sub.request(1);
69 | Socket socket1 = connect();
70 | receiveConnection();
71 | readWriteData(socket1, 1);
72 |
73 | // Check back pressure
74 | Socket socket2 = connect();
75 | subscriber.expectNoElements();
76 |
77 | // Now request the next connection
78 | sub.request(1);
79 | receiveConnection();
80 | readWriteData(socket2, 2);
81 |
82 | // Close the channel
83 | channel.close();
84 | subscriber.expectNoElements();
85 | subscriber.expectComplete();
86 | }
87 |
88 | private Socket connect() throws Exception {
89 | InetSocketAddress address = (InetSocketAddress) channel.localAddress();
90 | return new Socket(address.getAddress(), address.getPort());
91 | }
92 |
93 | private void readWriteData(Socket socket, int data) throws Exception {
94 | OutputStream os = socket.getOutputStream();
95 | os.write(data);
96 | os.flush();
97 | InputStream is = socket.getInputStream();
98 | int received = is.read();
99 | socket.close();
100 | assertEquals(received, data);
101 | }
102 |
103 | private void receiveConnection() throws Exception {
104 | Channel channel = subscriber.take();
105 | channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
106 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
107 | ctx.writeAndFlush(msg);
108 | }
109 | });
110 | group.register(channel);
111 | }
112 |
113 | private class SubscriberProbe implements Subscriber {
114 | final BlockingQueue subscriptions = new LinkedBlockingQueue<>();
115 | final BlockingQueue elements = new LinkedBlockingQueue<>();
116 | final Promise promise = new DefaultPromise<>(group.next());
117 |
118 | public void onSubscribe(Subscription s) {
119 | subscriptions.add(s);
120 | }
121 |
122 | public void onNext(T t) {
123 | elements.add(t);
124 | }
125 |
126 | public void onError(Throwable t) {
127 | promise.setFailure(t);
128 | }
129 |
130 | public void onComplete() {
131 | promise.setSuccess(null);
132 | }
133 |
134 | Subscription takeSubscription() throws Exception {
135 | Subscription sub = subscriptions.poll(100, TimeUnit.MILLISECONDS);
136 | assertNotNull(sub);
137 | return sub;
138 | }
139 |
140 | T take() throws Exception {
141 | T t = elements.poll(1000, TimeUnit.MILLISECONDS);
142 | assertNotNull(t);
143 | return t;
144 | }
145 |
146 | void expectNoElements() throws Exception {
147 | T t = elements.poll(100, TimeUnit.MILLISECONDS);
148 | assertNull(t);
149 | }
150 |
151 | void expectComplete() throws Exception {
152 | promise.get(100, TimeUnit.MILLISECONDS);
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/netty-reactive-streams-http/src/main/java/org/playframework/netty/http/HttpStreamsClientHandler.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty.http;
2 |
3 | import org.playframework.netty.CancelledSubscriber;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelPromise;
6 | import io.netty.handler.codec.http.*;
7 | import io.netty.util.ReferenceCountUtil;
8 | import org.reactivestreams.Publisher;
9 | import org.reactivestreams.Subscriber;
10 | import org.reactivestreams.Subscription;
11 |
12 | /**
13 | * Handler that converts written {@link StreamedHttpRequest} messages into {@link HttpRequest} messages
14 | * followed by {@link HttpContent} messages and reads {@link HttpResponse} messages followed by
15 | * {@link HttpContent} messages and produces {@link StreamedHttpResponse} messages.
16 | *
17 | * This allows request and response bodies to be handled using reactive streams.
18 | *
19 | * There are two types of messages that this handler accepts for writing, {@link StreamedHttpRequest} and
20 | * {@link FullHttpRequest}. Writing any other messages may potentially lead to HTTP message mangling.
21 | *
22 | * There are two types of messages that this handler will send down the chain, {@link StreamedHttpResponse},
23 | * and {@link FullHttpResponse}. If {@link io.netty.channel.ChannelOption#AUTO_READ} is false for the channel,
24 | * then any {@link StreamedHttpResponse} messages must be subscribed to consume the body, otherwise
25 | * it's possible that no read will be done of the messages.
26 | *
27 | * As long as messages are returned in the order that they arrive, this handler implicitly supports HTTP
28 | * pipelining.
29 | */
30 | public class HttpStreamsClientHandler extends HttpStreamsHandler {
31 |
32 | private int inFlight = 0;
33 | private int withServer = 0;
34 | private ChannelPromise closeOnZeroInFlight = null;
35 | private Subscriber awaiting100Continue;
36 | private StreamedHttpMessage awaiting100ContinueMessage;
37 | private boolean ignoreResponseBody = false;
38 |
39 | public HttpStreamsClientHandler() {
40 | super(HttpResponse.class, HttpRequest.class);
41 | }
42 |
43 | @Override
44 | protected boolean hasBody(HttpResponse response) {
45 | if (response.status().code() >= 100 && response.status().code() < 200) {
46 | return false;
47 | }
48 |
49 | if (response.status().equals(HttpResponseStatus.NO_CONTENT) ||
50 | response.status().equals(HttpResponseStatus.NOT_MODIFIED)) {
51 | return false;
52 | }
53 |
54 | if (HttpUtil.isTransferEncodingChunked(response)) {
55 | return true;
56 | }
57 |
58 |
59 | if (HttpUtil.isContentLengthSet(response)) {
60 | return HttpUtil.getContentLength(response) > 0;
61 | }
62 |
63 | return true;
64 | }
65 |
66 | @Override
67 | public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception {
68 | if (inFlight == 0) {
69 | ctx.close(future);
70 | } else {
71 | closeOnZeroInFlight = future;
72 | }
73 | }
74 |
75 | @Override
76 | protected void consumedInMessage(ChannelHandlerContext ctx) {
77 | inFlight--;
78 | withServer--;
79 | if (inFlight == 0 && closeOnZeroInFlight != null) {
80 | ctx.close(closeOnZeroInFlight);
81 | }
82 | }
83 |
84 | @Override
85 | protected void receivedOutMessage(ChannelHandlerContext ctx) {
86 | inFlight++;
87 | }
88 |
89 | @Override
90 | protected void sentOutMessage(ChannelHandlerContext ctx) {
91 | withServer++;
92 | }
93 |
94 | @Override
95 | protected HttpResponse createEmptyMessage(HttpResponse response) {
96 | return new EmptyHttpResponse(response);
97 | }
98 |
99 | @Override
100 | protected HttpResponse createStreamedMessage(HttpResponse response, Publisher stream) {
101 | return new DelegateStreamedHttpResponse(response, stream);
102 | }
103 |
104 | @Override
105 | protected void subscribeSubscriberToStream(StreamedHttpMessage msg, Subscriber subscriber) {
106 | if (HttpUtil.is100ContinueExpected(msg)) {
107 | awaiting100Continue = subscriber;
108 | awaiting100ContinueMessage = msg;
109 | } else {
110 | super.subscribeSubscriberToStream(msg, subscriber);
111 | }
112 | }
113 |
114 | @Override
115 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
116 |
117 | if (msg instanceof HttpResponse && awaiting100Continue != null && withServer == 0) {
118 | HttpResponse response = (HttpResponse) msg;
119 | if (response.status().equals(HttpResponseStatus.CONTINUE)) {
120 | super.subscribeSubscriberToStream(awaiting100ContinueMessage, awaiting100Continue);
121 | awaiting100Continue = null;
122 | awaiting100ContinueMessage = null;
123 | if (msg instanceof FullHttpResponse) {
124 | ReferenceCountUtil.release(msg);
125 | } else {
126 | ignoreResponseBody = true;
127 | }
128 | } else {
129 | awaiting100ContinueMessage.subscribe(new CancelledSubscriber());
130 | awaiting100ContinueMessage = null;
131 | awaiting100Continue.onSubscribe(new Subscription() {
132 | public void request(long n) {
133 | }
134 | public void cancel() {
135 | }
136 | });
137 | awaiting100Continue.onComplete();
138 | awaiting100Continue = null;
139 | super.channelRead(ctx, msg);
140 | }
141 | } else if (ignoreResponseBody && msg instanceof HttpContent) {
142 |
143 | ReferenceCountUtil.release(msg);
144 | if (msg instanceof LastHttpContent) {
145 | ignoreResponseBody = false;
146 | }
147 | } else {
148 | super.channelRead(ctx, msg);
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/netty-reactive-streams/src/test/java/org/playframework/netty/HandlerPublisherVerificationTest.java:
--------------------------------------------------------------------------------
1 | package org.playframework.netty;
2 |
3 | import io.netty.channel.ChannelFuture;
4 | import io.netty.channel.ChannelFutureListener;
5 | import io.netty.channel.DefaultEventLoopGroup;
6 | import io.netty.channel.local.LocalChannel;
7 | import org.reactivestreams.Publisher;
8 | import org.reactivestreams.tck.PublisherVerification;
9 | import org.reactivestreams.tck.TestEnvironment;
10 | import org.testng.annotations.*;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 | import java.util.concurrent.Executors;
16 | import java.util.concurrent.ScheduledExecutorService;
17 |
18 | public class HandlerPublisherVerificationTest extends PublisherVerification {
19 |
20 | private final int batchSize;
21 | // The number of elements to publish initially, before the subscriber is received
22 | private final int publishInitial;
23 | // Whether we should use scheduled publishing (with a small delay)
24 | private final boolean scheduled;
25 |
26 | private ScheduledExecutorService executor;
27 | private DefaultEventLoopGroup eventLoop;
28 |
29 | // For debugging, change the data provider to simple, and adjust the parameters below
30 | @Factory(dataProvider = "noScheduled")
31 | public HandlerPublisherVerificationTest(int batchSize, int publishInitial, boolean scheduled) {
32 | super(new TestEnvironment(200));
33 | this.batchSize = batchSize;
34 | this.publishInitial = publishInitial;
35 | this.scheduled = scheduled;
36 | }
37 |
38 | @DataProvider
39 | public static Object[][] simple() {
40 | boolean scheduled = false;
41 | int batchSize = 2;
42 | int publishInitial = 0;
43 | return new Object[][] {
44 | new Object[] {batchSize, publishInitial, scheduled}
45 | };
46 | }
47 |
48 | @DataProvider
49 | public static Object[][] full() {
50 | List