/*
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient.reactivestreams;
import static org.asynchttpclient.Dsl.*;
import static org.asynchttpclient.test.TestUtils.*;
import static org.testng.Assert.assertEquals;
import io.netty.handler.codec.http.HttpHeaders;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import org.asynchttpclient.AbstractBasicTest;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.BoundRequestBuilder;
import org.asynchttpclient.HttpResponseBodyPart;
import org.asynchttpclient.HttpResponseStatus;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.asynchttpclient.handler.StreamedAsyncHandler;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.testng.annotations.Test;
import rx.Observable;
import rx.RxReactiveStreams;
public class ReactiveStreamsTest extends AbstractBasicTest {
@Test(groups = "standalone")
public void testStreamingPutImage() throws Exception {
try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(100 * 6000))) {
Response response = client.preparePut(getTargetUrl()).setBody(LARGE_IMAGE_PUBLISHER).execute().get();
assertEquals(response.getStatusCode(), 200);
assertEquals(response.getResponseBodyAsBytes(), LARGE_IMAGE_BYTES);
}
}
@Test(groups = "standalone")
public void testConnectionDoesNotGetClosed() throws Exception {
// test that we can stream the same request multiple times
try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(100 * 6000))) {
BoundRequestBuilder requestBuilder = client.preparePut(getTargetUrl()).setBody(LARGE_IMAGE_PUBLISHER);
Response response = requestBuilder.execute().get();
assertEquals(response.getStatusCode(), 200);
assertEquals(response.getResponseBodyAsBytes().length, LARGE_IMAGE_BYTES.length);
assertEquals(response.getResponseBodyAsBytes(), LARGE_IMAGE_BYTES);
response = requestBuilder.execute().get();
assertEquals(response.getStatusCode(), 200);
assertEquals(response.getResponseBodyAsBytes().length, LARGE_IMAGE_BYTES.length);
assertEquals(response.getResponseBodyAsBytes(), LARGE_IMAGE_BYTES);
}
}
@Test(groups = "standalone", expectedExceptions = ExecutionException.class)
public void testFailingStream() throws Exception {
try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(100 * 6000))) {
Observable<ByteBuffer> failingObservable = Observable.error(new FailedStream());
Publisher<ByteBuffer> failingPublisher = RxReactiveStreams.toPublisher(failingObservable);
client.preparePut(getTargetUrl()).setBody(failingPublisher).execute().get();
}
}
@SuppressWarnings("serial")
private class FailedStream extends RuntimeException {
}
@Test(groups = "standalone")
public void streamedResponseTest() throws Throwable {
try (AsyncHttpClient c = asyncHttpClient()) {
ListenableFuture<SimpleStreamedAsyncHandler> future = c.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_BYTES).execute(new SimpleStreamedAsyncHandler());
assertEquals(future.get().getBytes(), LARGE_IMAGE_BYTES);
// Run it again to check that the pipeline is in a good state
future = c.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_BYTES).execute(new SimpleStreamedAsyncHandler());
assertEquals(future.get().getBytes(), LARGE_IMAGE_BYTES);
// Make sure a regular request still works
assertEquals(c.preparePost(getTargetUrl()).setBody("Hello").execute().get().getResponseBody(), "Hello");
}
}
@Test(groups = "standalone")
public void cancelStreamedResponseTest() throws Throwable {
try (AsyncHttpClient c = asyncHttpClient()) {
// Cancel immediately
c.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_BYTES).execute(new CancellingStreamedAsyncProvider(0)).get();
// Cancel after 1 element
c.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_BYTES).execute(new CancellingStreamedAsyncProvider(1)).get();
// Cancel after 10 elements
c.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_BYTES).execute(new CancellingStreamedAsyncProvider(10)).get();
// Make sure a regular request works
assertEquals(c.preparePost(getTargetUrl()).setBody("Hello").execute().get().getResponseBody(), "Hello");
}
}
static protected class SimpleStreamedAsyncHandler implements StreamedAsyncHandler<SimpleStreamedAsyncHandler> {
private final SimpleSubscriber<HttpResponseBodyPart> subscriber;
public SimpleStreamedAsyncHandler() {
this(new SimpleSubscriber<>());
}
public SimpleStreamedAsyncHandler(SimpleSubscriber<HttpResponseBodyPart> subscriber) {
this.subscriber = subscriber;
}
@Override
public State onStream(Publisher<HttpResponseBodyPart> publisher) {
publisher.subscribe(subscriber);
return State.CONTINUE;
}
@Override
public void onThrowable(Throwable t) {
throw new AssertionError(t);
}
@Override
public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
throw new AssertionError("Should not have received body part");
}
@Override
public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception {
return State.CONTINUE;
}
@Override
public State onHeadersReceived(HttpHeaders headers) throws Exception {
return State.CONTINUE;
}
@Override
public SimpleStreamedAsyncHandler onCompleted() throws Exception {
return this;
}
public byte[] getBytes() throws Throwable {
List<HttpResponseBodyPart> bodyParts = subscriber.getElements();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
for (HttpResponseBodyPart part : bodyParts) {
bytes.write(part.getBodyPartBytes());
}
return bytes.toByteArray();
}
}
/**
* Simple subscriber that requests and buffers one element at a time.
*/
static protected class SimpleSubscriber<T> implements Subscriber<T> {
private volatile Subscription subscription;
private volatile Throwable error;
private final List<T> elements = Collections.synchronizedList(new ArrayList<>());
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(T t) {
elements.add(t);
subscription.request(1);
}
@Override
public void onError(Throwable error) {
this.error = error;
latch.countDown();
}
@Override
public void onComplete() {
latch.countDown();
}
public List<T> getElements() throws Throwable {
latch.await();
if (error != null) {
throw error;
} else {
return elements;
}
}
}
static class CancellingStreamedAsyncProvider implements StreamedAsyncHandler<CancellingStreamedAsyncProvider> {
private final int cancelAfter;
public CancellingStreamedAsyncProvider(int cancelAfter) {
this.cancelAfter = cancelAfter;
}
@Override
public State onStream(Publisher<HttpResponseBodyPart> publisher) {
publisher.subscribe(new CancellingSubscriber<>(cancelAfter));
return State.CONTINUE;
}
@Override
public void onThrowable(Throwable t) {
throw new AssertionError(t);
}
@Override
public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
throw new AssertionError("Should not have received body part");
}
@Override
public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception {
return State.CONTINUE;
}
@Override
public State onHeadersReceived(HttpHeaders headers) throws Exception {
return State.CONTINUE;
}
@Override
public CancellingStreamedAsyncProvider onCompleted() throws Exception {
return this;
}
}
/**
* Simple subscriber that cancels after receiving n elements.
*/
static class CancellingSubscriber<T> implements Subscriber<T> {
private final int cancelAfter;
public CancellingSubscriber(int cancelAfter) {
this.cancelAfter = cancelAfter;
}
private volatile Subscription subscription;
private volatile int count;
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
if (cancelAfter == 0) {
subscription.cancel();
} else {
subscription.request(1);
}
}
@Override
public void onNext(T t) {
count++;
if (count == cancelAfter) {
subscription.cancel();
} else {
subscription.request(1);
}
}
@Override
public void onError(Throwable error) {
}
@Override
public void onComplete() {
}
}
}