README.md 0100664 0000000 0000000 00000006627 14157612023 011102 0 ustar 00 0000000 0000000
Apache HttpComponents Core
==========================
Welcome to the HttpCore component of the Apache HttpComponents project.
[](https://travis-ci.com/apache/httpcomponents-core)
[](https://maven-badges.herokuapp.com/maven-central/org.apache.httpcomponents.core5/httpcore5)
[](https://opensource.org/licenses/Apache-2.0)
Building Instructions
---------------------
For building from source instructions please refer to [BUILDING.txt](./BUILDING.txt).
Dependencies
------------
HttpCore requires Java 1.7 compatible runtime.
Licensing
---------
Apache HttpComponents Core is licensed under the Apache License 2.0.
See the files [LICENSE.txt](./LICENSE.txt) and [NOTICE.txt](./NOTICE.txt) for more information.
Contact
-------
- For general information visit the main project site at
https://hc.apache.org/
- For current status information visit the status page at
https://hc.apache.org/status.html
- If you want to contribute visit
https://hc.apache.org/get-involved.html
Cryptographic Software Notice
-----------------------------
This distribution may include software that has been designed for use
with cryptographic software. The country in which you currently reside
may have restrictions on the import, possession, use, and/or re-export
to another country, of encryption software. BEFORE using any encryption
software, please check your country's laws, regulations and policies
concerning the import, possession, or use, and re-export of encryption
software, to see if this is permitted. See https://www.wassenaar.org/
for more information.
The U.S. Government Department of Commerce, Bureau of Industry and
Security (BIS), has classified this software as Export Commodity
Control Number (ECCN) 5D002.C.1, which includes information security
software using or performing cryptographic functions with asymmetric
algorithms. The form and manner of this Apache Software Foundation
distribution makes it eligible for export under the License Exception
ENC Technology Software Unrestricted (TSU) exception (see the BIS
Export Administration Regulations, Section 740.13) for both object
code and source code.
The following provides more details on the included software that
may be subject to export controls on cryptographic software:
> Apache HttpComponents Core interfaces with the
> Java Secure Socket Extension (JSSE) API to provide
> - HTTPS support
>
> Apache HttpComponents Core does not include any
> implementation of JSSE.
httpcore5-reactive/ 0040755 0000000 0000000 00000000000 14157707252 013337 5 ustar 00 0000000 0000000 httpcore5-reactive/src/ 0040755 0000000 0000000 00000000000 13432040502 014105 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/ 0040755 0000000 0000000 00000000000 13342255427 015102 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/ 0040755 0000000 0000000 00000000000 13342255427 016023 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/ 0040755 0000000 0000000 00000000000 13342255427 016612 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/ 0040755 0000000 0000000 00000000000 13342255427 020033 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/ 0040755 0000000 0000000 00000000000 13342255427 020425 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/ 0040755 0000000 0000000 00000000000 13342255427 021442 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/ 0040755 0000000 0000000 00000000000 14157612023 023236 5 ustar 00 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/WritableByteChannelMock.java 0100644 0000000 0000000 00000007512 13342255427 030611 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
public class WritableByteChannelMock implements WritableByteChannel {
private final int capacityLimit;
private int capacityUsed;
private ByteBuffer buf;
private boolean closed;
public WritableByteChannelMock(final int initialSize, final int capacityLimit) {
this.buf = ByteBuffer.allocate(initialSize);
this.capacityLimit = capacityLimit;
}
public WritableByteChannelMock(final int initialSize) {
this(initialSize, 0);
}
private void expandCapacity(final int capacity) {
final ByteBuffer oldbuffer = this.buf;
this.buf = ByteBuffer.allocate(capacity);
oldbuffer.flip();
this.buf.put(oldbuffer);
}
private void ensureCapacity(final int requiredCapacity) {
if (requiredCapacity > this.buf.capacity()) {
expandCapacity(requiredCapacity);
}
}
@Override
public int write(final ByteBuffer src) throws IOException {
if (this.closed) {
throw new ClosedChannelException();
}
final int len = src.remaining();
ensureCapacity(this.buf.position() + len);
if (this.capacityLimit > 0) {
final int chunk = Math.min(this.capacityLimit - this.capacityUsed, len);
if (chunk > 0) {
final int limit = src.limit();
src.limit(src.position() + chunk);
this.buf.put(src);
src.limit(limit);
this.capacityUsed += chunk;
return chunk;
}
return 0;
}
this.buf.put(src);
return len;
}
@Override
public boolean isOpen() {
return !this.closed;
}
@Override
public void close() throws IOException {
this.closed = true;
}
public void flush() {
this.capacityUsed = 0;
}
public void reset() {
this.capacityUsed = 0;
this.buf.clear();
}
public byte[] toByteArray() {
final ByteBuffer dup = this.buf.duplicate();
dup.flip();
final byte[] bytes = new byte[dup.remaining()];
dup.get(bytes);
return bytes;
}
public String dump(final Charset charset) throws CharacterCodingException {
this.buf.flip();
final CharBuffer charBuffer = charset.newDecoder().decode(this.buf);
this.buf.compact();
return charBuffer.toString();
}
}
httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java 0100664 0000000 0000000 00000007217 14157612023 031007 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.apache.hc.core5.http.HttpStreamResetException;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import org.junit.Assert;
import org.junit.Test;
import io.reactivex.Flowable;
public class TestReactiveDataProducer {
@Test
public void testStreamThatEndsNormally() throws Exception {
final Flowable publisher = Flowable.just(
ByteBuffer.wrap(new byte[]{ '1', '2', '3' }),
ByteBuffer.wrap(new byte[]{ '4', '5', '6' }));
final ReactiveDataProducer producer = new ReactiveDataProducer(publisher);
final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024);
final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel);
producer.produce(streamChannel);
Assert.assertTrue(byteChannel.isOpen());
Assert.assertEquals("123456", byteChannel.dump(StandardCharsets.US_ASCII));
producer.produce(streamChannel);
Assert.assertFalse(byteChannel.isOpen());
Assert.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII));
}
@Test
public void testStreamThatEndsWithError() throws Exception {
final Flowable publisher = Flowable.concatArray(
Flowable.just(
ByteBuffer.wrap(new byte[]{ '1' }),
ByteBuffer.wrap(new byte[]{ '2' }),
ByteBuffer.wrap(new byte[]{ '3' }),
ByteBuffer.wrap(new byte[]{ '4' }),
ByteBuffer.wrap(new byte[]{ '5' }),
ByteBuffer.wrap(new byte[]{ '6' })),
Flowable.error(new RuntimeException())
);
final ReactiveDataProducer producer = new ReactiveDataProducer(publisher);
final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024);
final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel);
producer.produce(streamChannel);
Assert.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII));
try {
producer.produce(streamChannel);
Assert.fail("Expected ProtocolException");
} catch (final HttpStreamResetException ex) {
Assert.assertTrue("Expected published exception to be rethrown", ex.getCause() instanceof RuntimeException);
Assert.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII));
}
}
}
httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ 0040755 0000000 0000000 00000000000 14157612023 025054 5 ustar 00 0000000 0000000 ././@LongLink 0100644 0000000 0000000 00000000154 14157612023 011634 L ustar 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexClientExample.java httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexClientExamp0100664 0000000 0000000 00000015227 14157612023 032706 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive.examples;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.Message;
import org.apache.hc.core5.http.impl.Http1StreamListener;
import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
import org.apache.hc.core5.http.message.RequestLine;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.reactive.ReactiveEntityProducer;
import org.apache.hc.core5.reactive.ReactiveResponseConsumer;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.util.Timeout;
import org.reactivestreams.Publisher;
import io.reactivex.Flowable;
import io.reactivex.Notification;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
/**
* Example of full-duplex HTTP/1.1 message exchanges using reactive streaming. This demo will stream randomly
* generated text to the server via a POST request, while writing the response stream's events to standard output.
* This demo works out-of-the-box with {@link ReactiveFullDuplexServerExample}.
*/
public class ReactiveFullDuplexClientExample {
public static void main(final String[] args) throws Exception {
String endpoint = "http://localhost:8080/echo";
if (args.length >= 1) {
endpoint = args[0];
}
// Create and start requester
final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap()
.setIOReactorConfig(IOReactorConfig.custom().setSoTimeout(5, TimeUnit.SECONDS).build())
.setStreamListener(new Http1StreamListener() {
@Override
public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request));
}
@Override
public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response));
}
@Override
public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
if (keepAlive) {
System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)");
} else {
System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)");
}
}
})
.create();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("HTTP requester shutting down");
requester.close(CloseMode.GRACEFUL);
}
});
requester.start();
final Random random = new Random();
final Flowable publisher = Flowable.range(1, 100)
.map(new Function() {
@Override
public ByteBuffer apply(final Integer ignored) {
final String str = random.nextDouble() + "\n";
return ByteBuffer.wrap(str.getBytes(UTF_8));
}
});
final AsyncRequestProducer requestProducer = AsyncRequestBuilder.post(new URI(endpoint))
.setEntity(new ReactiveEntityProducer(publisher, -1, ContentType.TEXT_PLAIN, null))
.build();
final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer();
final Future responseComplete = requester.execute(requestProducer, consumer, Timeout.ofSeconds(30), null);
final Message> streamingResponse = consumer.getResponseFuture().get();
System.out.println(streamingResponse.getHead());
for (final Header header : streamingResponse.getHead().getHeaders()) {
System.out.println(header);
}
System.out.println();
Observable.fromPublisher(streamingResponse.getBody())
.map(new Function() {
@Override
public String apply(final ByteBuffer byteBuffer) {
final byte[] string = new byte[byteBuffer.remaining()];
byteBuffer.get(string);
return new String(string);
}
})
.materialize()
.forEach(new Consumer>() {
@Override
public void accept(final Notification byteBufferNotification) {
System.out.println(byteBufferNotification);
}
});
responseComplete.get(1, TimeUnit.MINUTES);
System.out.println("Shutting down I/O reactor");
requester.initiateShutdown();
}
}
././@LongLink 0100644 0000000 0000000 00000000154 14157612023 011634 L ustar 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.java httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExamp0100664 0000000 0000000 00000015475 14157612023 032743 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive.examples;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.function.Supplier;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.impl.BasicEntityDetails;
import org.apache.hc.core5.http.impl.Http1StreamListener;
import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.http.message.RequestLine;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
import org.apache.hc.core5.http.nio.ResponseChannel;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.reactive.ReactiveRequestProcessor;
import org.apache.hc.core5.reactive.ReactiveServerExchangeHandler;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.reactor.ListenerEndpoint;
import org.apache.hc.core5.util.TimeValue;
import org.reactivestreams.Publisher;
/**
* Example of full-duplex HTTP/1.1 message exchanges using reactive streaming. This demo server works out-of-the-box
* with {@link ReactiveFullDuplexClientExample}; it can also be invoked interactively using telnet.
*/
public class ReactiveFullDuplexServerExample {
public static void main(final String[] args) throws Exception {
int port = 8080;
if (args.length >= 1) {
port = Integer.parseInt(args[0]);
}
final IOReactorConfig config = IOReactorConfig.custom()
.setSoTimeout(15, TimeUnit.SECONDS)
.setTcpNoDelay(true)
.build();
final HttpAsyncServer server = AsyncServerBootstrap.bootstrap()
.setIOReactorConfig(config)
.setStreamListener(new Http1StreamListener() {
@Override
public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request));
}
@Override
public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response));
}
@Override
public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
if (keepAlive) {
System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)");
} else {
System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)");
}
}
})
.register("/echo", new Supplier() {
@Override
public AsyncServerExchangeHandler get() {
return new ReactiveServerExchangeHandler(new ReactiveRequestProcessor() {
@Override
public void processRequest(
final HttpRequest request,
final EntityDetails entityDetails,
final ResponseChannel responseChannel,
final HttpContext context,
final Publisher requestBody,
final Callback> responseBodyFuture
) throws HttpException, IOException {
if (new BasicHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE).equals(request.getHeader(HttpHeaders.EXPECT))) {
responseChannel.sendInformation(new BasicHttpResponse(100), context);
}
responseChannel.sendResponse(
new BasicHttpResponse(200),
new BasicEntityDetails(-1, ContentType.APPLICATION_OCTET_STREAM),
context);
// Simply using the request publisher as the response publisher will
// cause the server to echo the request body.
responseBodyFuture.execute(requestBody);
}
});
}
})
.create();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("HTTP server shutting down");
server.close(CloseMode.GRACEFUL);
}
});
server.start();
final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
final ListenerEndpoint listenerEndpoint = future.get();
System.out.print("Listening on " + listenerEndpoint.getAddress());
server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE));
}
}
httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java 0100664 0000000 0000000 00000020572 14157612023 031016 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
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.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.core5.http.HttpStreamResetException;
import org.apache.hc.core5.http.nio.CapacityChannel;
import org.junit.Assert;
import org.junit.Test;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import io.reactivex.Flowable;
import io.reactivex.Notification;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.functions.Consumer;
public class TestReactiveDataConsumer {
@Test
public void testStreamThatEndsNormally() throws Exception {
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
final List output = Collections.synchronizedList(new ArrayList());
final CountDownLatch complete = new CountDownLatch(1);
Observable.fromPublisher(consumer)
.materialize()
.forEach(new Consumer>() {
@Override
public void accept(final Notification byteBufferNotification) throws Exception {
if (byteBufferNotification.isOnComplete()) {
complete.countDown();
} else if (byteBufferNotification.isOnNext()) {
output.add(byteBufferNotification.getValue());
} else {
throw new IllegalArgumentException();
}
}
});
consumer.consume(ByteBuffer.wrap(new byte[]{ '1' }));
consumer.consume(ByteBuffer.wrap(new byte[]{ '2' }));
consumer.consume(ByteBuffer.wrap(new byte[]{ '3' }));
consumer.streamEnd(null);
Assert.assertTrue("Stream did not finish before timeout", complete.await(1, TimeUnit.SECONDS));
Assert.assertEquals(3, output.size());
Assert.assertEquals(ByteBuffer.wrap(new byte[]{ '1' }), output.get(0));
Assert.assertEquals(ByteBuffer.wrap(new byte[]{ '2' }), output.get(1));
Assert.assertEquals(ByteBuffer.wrap(new byte[]{ '3' }), output.get(2));
}
@Test
public void testStreamThatEndsWithError() {
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
final Single>> single = Observable.fromPublisher(consumer)
.materialize()
.toList();
final Exception ex = new RuntimeException();
consumer.failed(ex);
Assert.assertSame(ex, single.blockingGet().get(0).getError());
}
@Test(expected = HttpStreamResetException.class)
public void testCancellation() throws Exception {
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
consumer.subscribe(new Subscriber() {
@Override
public void onSubscribe(final Subscription s) {
s.cancel();
}
@Override
public void onNext(final ByteBuffer byteBuffer) {
}
@Override
public void onError(final Throwable throwable) {
}
@Override
public void onComplete() {
}
});
consumer.consume(ByteBuffer.wrap(new byte[1024]));
}
@Test
public void testCapacityIncrements() throws Exception {
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
final ByteBuffer data = ByteBuffer.wrap(new byte[1024]);
final AtomicInteger lastIncrement = new AtomicInteger(-1);
final CapacityChannel channel = new CapacityChannel() {
@Override
public void update(final int increment) {
lastIncrement.set(increment);
}
};
consumer.updateCapacity(channel);
Assert.assertEquals("CapacityChannel#update should not have been invoked yet", -1, lastIncrement.get());
final AtomicInteger received = new AtomicInteger(0);
final AtomicReference subscription = new AtomicReference<>();
consumer.subscribe(new Subscriber() {
@Override
public void onSubscribe(final Subscription s) {
subscription.set(s);
}
@Override
public void onNext(final ByteBuffer byteBuffer) {
received.incrementAndGet();
}
@Override
public void onError(final Throwable throwable) {
}
@Override
public void onComplete() {
}
});
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
subscription.get().request(1);
Assert.assertEquals(1024, lastIncrement.get());
subscription.get().request(2);
Assert.assertEquals(2 * 1024, lastIncrement.get());
subscription.get().request(99);
Assert.assertEquals(1024, lastIncrement.get());
}
@Test
public void testFullResponseBuffering() throws Exception {
// Due to inherent race conditions, is possible for the entire response to be buffered and completed before
// the Subscriber shows up. This must be handled correctly.
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
final ByteBuffer data = ByteBuffer.wrap(new byte[1024]);
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.streamEnd(null);
Assert.assertEquals(Flowable.fromPublisher(consumer).count().blockingGet().longValue(), 3L);
}
@Test
public void testErrorBuffering() throws Exception {
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
final ByteBuffer data = ByteBuffer.wrap(new byte[1024]);
final RuntimeException ex = new RuntimeException();
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.consume(data.duplicate());
consumer.failed(ex);
final Notification result = Flowable.fromPublisher(consumer)
.materialize()
.singleOrError()
.blockingGet();
Assert.assertSame(ex, result.getError());
}
@Test
public void testFailAfterCompletion() {
// Calling consumer.failed() after consumer.streamEnd() must be a no-op.
// The exception must be discarded, and the subscriber must see that
// the stream was successfully completed.
final ReactiveDataConsumer consumer = new ReactiveDataConsumer();
consumer.streamEnd(null);
final RuntimeException ex = new RuntimeException();
consumer.failed(ex);
final Notification result = Flowable.fromPublisher(consumer)
.materialize()
.singleOrError()
.blockingGet();
Assert.assertFalse(result.isOnError());
Assert.assertTrue(result.isOnComplete());
}
}
httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java 0100644 0000000 0000000 00000004556 13342255427 030376 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
public class BasicDataStreamChannel implements DataStreamChannel {
private final WritableByteChannel byteChannel;
private List trailers;
public BasicDataStreamChannel(final WritableByteChannel byteChannel) {
this.byteChannel = byteChannel;
}
@Override
public void requestOutput() {
}
@Override
public int write(final ByteBuffer src) throws IOException {
return byteChannel.write(src);
}
@Override
public void endStream() throws IOException {
if (byteChannel.isOpen()) {
byteChannel.close();
}
}
@Override
public void endStream(final List extends Header> trailers) throws IOException {
endStream();
if (trailers != null) {
this.trailers = new ArrayList<>();
this.trailers.addAll(trailers);
}
}
public List getTrailers() {
return trailers;
}
}
httpcore5-reactive/src/main/ 0040755 0000000 0000000 00000000000 13342255427 015047 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/ 0040755 0000000 0000000 00000000000 13342255427 015770 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/ 0040755 0000000 0000000 00000000000 13342255427 016557 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/apache/ 0040755 0000000 0000000 00000000000 13342255427 020000 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/ 0040755 0000000 0000000 00000000000 13342255427 020372 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/ 0040755 0000000 0000000 00000000000 13342255427 021407 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ 0040755 0000000 0000000 00000000000 14157612023 023203 5 ustar 00 0000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java 0100664 0000000 0000000 00000014656 14114632321 030125 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStreamResetException;
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
import org.apache.hc.core5.http.nio.CapacityChannel;
import org.apache.hc.core5.util.Args;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
/**
* An asynchronous data consumer that supports Reactive Streams.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.SAFE)
final class ReactiveDataConsumer implements AsyncDataConsumer, Publisher {
private final AtomicLong requests = new AtomicLong(0);
private final BlockingQueue buffers = new LinkedBlockingQueue<>();
private final AtomicBoolean flushInProgress = new AtomicBoolean(false);
private final Object flushLock = new Object();
private final AtomicInteger windowScalingIncrement = new AtomicInteger(0);
private volatile boolean cancelled;
private volatile boolean completed;
private volatile Exception exception;
private volatile CapacityChannel capacityChannel;
private volatile Subscriber super ByteBuffer> subscriber;
public void failed(final Exception cause) {
if (!completed) {
exception = cause;
flushToSubscriber();
}
}
@Override
public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
throwIfCancelled();
this.capacityChannel = capacityChannel;
signalCapacity(capacityChannel);
}
private void signalCapacity(final CapacityChannel channel) throws IOException {
final int increment = windowScalingIncrement.getAndSet(0);
if (increment > 0) {
channel.update(increment);
}
}
private void throwIfCancelled() throws IOException {
if (cancelled) {
throw new HttpStreamResetException("Downstream subscriber to ReactiveDataConsumer cancelled");
}
}
@Override
public void consume(final ByteBuffer byteBuffer) throws IOException {
if (completed) {
throw new IllegalStateException("Received data past end of stream");
}
throwIfCancelled();
final byte[] copy = new byte[byteBuffer.remaining()];
byteBuffer.get(copy);
buffers.add(ByteBuffer.wrap(copy));
flushToSubscriber();
}
@Override
public void streamEnd(final List extends Header> trailers) {
completed = true;
flushToSubscriber();
}
@Override
public void releaseResources() {
this.capacityChannel = null;
}
private void flushToSubscriber() {
synchronized (flushLock) {
final Subscriber super ByteBuffer> s = subscriber;
if (flushInProgress.getAndSet(true)) {
return;
}
try {
if (s == null) {
return;
}
if (exception != null) {
subscriber = null;
s.onError(exception);
return;
}
ByteBuffer next;
while (requests.get() > 0 && ((next = buffers.poll()) != null)) {
final int bytesFreed = next.remaining();
s.onNext(next);
requests.decrementAndGet();
windowScalingIncrement.addAndGet(bytesFreed);
}
final CapacityChannel localChannel = capacityChannel;
if (localChannel != null) {
try {
signalCapacity(localChannel);
} catch (final IOException e) {
exception = e;
s.onError(e);
return;
}
}
if (completed && buffers.isEmpty()) {
subscriber = null;
s.onComplete();
}
} finally {
flushInProgress.set(false);
}
}
}
@Override
public void subscribe(final Subscriber super ByteBuffer> subscriber) {
this.subscriber = Args.notNull(subscriber, "subscriber");
subscriber.onSubscribe(new Subscription() {
@Override
public void request(final long increment) {
if (increment <= 0) {
failed(new IllegalArgumentException("The number of elements requested must be strictly positive"));
return;
}
requests.addAndGet(increment);
flushToSubscriber();
}
@Override
public void cancel() {
ReactiveDataConsumer.this.cancelled = true;
ReactiveDataConsumer.this.subscriber = null;
}
});
}
}
httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveResponseConsumer.java 0100664 0000000 0000000 00000014145 14157612023 031047 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.BasicFuture;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.Message;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.CapacityChannel;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Args;
import org.reactivestreams.Publisher;
/**
* An {@link AsyncResponseConsumer} that publishes the response body through
* a {@link Publisher}, as defined by the Reactive Streams specification. The
* response is represented as a {@link Message} consisting of a {@link
* HttpResponse} representing the headers and a {@link Publisher} representing
* the response body as an asynchronous stream of {@link ByteBuffer} instances.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.SAFE)
public final class ReactiveResponseConsumer implements AsyncResponseConsumer {
private final ReactiveDataConsumer reactiveDataConsumer = new ReactiveDataConsumer();
private final List trailers = Collections.synchronizedList(new ArrayList());
private final BasicFuture>> responseFuture;
private volatile BasicFuture responseCompletion;
private volatile HttpResponse informationResponse;
private volatile EntityDetails entityDetails;
/**
* Creates a {@code ReactiveResponseConsumer}.
*/
public ReactiveResponseConsumer() {
this.responseFuture = new BasicFuture<>(null);
}
/**
* Creates a {@code ReactiveResponseConsumer} that will call back the supplied {@link FutureCallback} with a
* streamable response.
*
* @param responseCallback the callback to invoke when the response is available for consumption.
*/
public ReactiveResponseConsumer(final FutureCallback>> responseCallback) {
this.responseFuture = new BasicFuture<>(Args.notNull(responseCallback, "responseCallback"));
}
public Future>> getResponseFuture() {
return responseFuture;
}
/**
* Returns the intermediate (1xx) HTTP response if one was received.
*
* @return the information response, or {@code null} if none.
*/
public HttpResponse getInformationResponse() {
return informationResponse;
}
/**
* Returns the response entity details.
*
* @return the entity details, or {@code null} if none.
*/
public EntityDetails getEntityDetails() {
return entityDetails;
}
/**
* Returns the trailers received at the end of the response.
*
* @return a non-null list of zero or more trailers.
*/
public List getTrailers() {
return trailers;
}
@Override
public void consumeResponse(
final HttpResponse response,
final EntityDetails entityDetails,
final HttpContext httpContext,
final FutureCallback resultCallback
) {
this.entityDetails = entityDetails;
this.responseCompletion = new BasicFuture<>(resultCallback);
this.responseFuture.completed(new Message>(response, reactiveDataConsumer));
if (entityDetails == null) {
streamEnd(null);
}
}
@Override
public void informationResponse(final HttpResponse response, final HttpContext httpContext) {
this.informationResponse = response;
}
@Override
public void failed(final Exception cause) {
reactiveDataConsumer.failed(cause);
responseFuture.failed(cause);
if (responseCompletion != null) {
responseCompletion.failed(cause);
}
}
@Override
public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
reactiveDataConsumer.updateCapacity(capacityChannel);
}
@Override
public void consume(final ByteBuffer src) throws IOException {
reactiveDataConsumer.consume(src);
}
@Override
public void streamEnd(final List extends Header> trailers) {
if (trailers != null) {
this.trailers.addAll(trailers);
}
reactiveDataConsumer.streamEnd(trailers);
responseCompletion.completed(null);
}
@Override
public void releaseResources() {
reactiveDataConsumer.releaseResources();
responseFuture.cancel();
if (responseCompletion != null) {
responseCompletion.cancel();
}
}
}
httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java 0100644 0000000 0000000 00000005133 13345221647 031066 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.nio.ResponseChannel;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.reactivestreams.Publisher;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* @since 5.0
*/
public interface ReactiveRequestProcessor {
/**
* Processes the actual HTTP request. The handler can choose to send
* response messages immediately inside the call or asynchronously
* at some later point.
*
* @param request the actual request.
* @param entityDetails the request entity details or {@code null} if the request
* does not enclose an entity.
* @param responseChannel the response channel.
* @param context the actual execution context.
* @param requestBody a reactive stream representing the request body.
* @param responseBodyCallback a callback to invoke with the response body, if any.
*/
void processRequest(
HttpRequest request,
EntityDetails entityDetails,
ResponseChannel responseChannel,
HttpContext context,
Publisher requestBody,
Callback> responseBodyCallback) throws HttpException, IOException;
}
httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveServerExchangeHandler.java 0100664 0000000 0000000 00000012007 14157612023 031737 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
import org.apache.hc.core5.http.nio.CapacityChannel;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import org.apache.hc.core5.http.nio.ResponseChannel;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.reactivestreams.Publisher;
/**
* An implementation of {@link AsyncServerExchangeHandler} designed to work with reactive streams.
*
* @since 5.0
*/
public final class ReactiveServerExchangeHandler implements AsyncServerExchangeHandler {
private final ReactiveRequestProcessor requestProcessor;
private final AtomicReference responseProducer = new AtomicReference<>();
private final ReactiveDataConsumer requestConsumer;
private volatile DataStreamChannel channel;
/**
* Creates a {@code ReactiveServerExchangeHandler}.
*
* @param requestProcessor the {@link ReactiveRequestProcessor} instance to
* invoke when the request is ready to be handled.
*/
public ReactiveServerExchangeHandler(final ReactiveRequestProcessor requestProcessor) {
this.requestProcessor = requestProcessor;
this.requestConsumer = new ReactiveDataConsumer();
}
@Override
public void handleRequest(
final HttpRequest request,
final EntityDetails entityDetails,
final ResponseChannel responseChannel,
final HttpContext context
) throws HttpException, IOException {
final Callback> callback = new Callback>() {
@Override
public void execute(final Publisher result) {
final ReactiveDataProducer producer = new ReactiveDataProducer(result);
if (channel != null) {
producer.setChannel(channel);
}
responseProducer.set(producer);
result.subscribe(producer);
}
};
requestProcessor.processRequest(request, entityDetails, responseChannel, context, requestConsumer, callback);
}
@Override
public void failed(final Exception cause) {
requestConsumer.failed(cause);
final ReactiveDataProducer p = responseProducer.get();
if (p != null) {
p.onError(cause);
}
}
@Override
public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
requestConsumer.updateCapacity(capacityChannel);
}
@Override
public void consume(final ByteBuffer src) throws IOException {
requestConsumer.consume(src);
}
@Override
public void streamEnd(final List extends Header> trailers) throws HttpException, IOException {
requestConsumer.streamEnd(trailers);
}
@Override
public int available() {
final ReactiveDataProducer p = responseProducer.get();
if (p == null) {
return 0;
} else {
return p.available();
}
}
@Override
public void produce(final DataStreamChannel channel) throws IOException {
this.channel = channel;
final ReactiveDataProducer p = responseProducer.get();
if (p != null) {
p.produce(channel);
}
}
@Override
public void releaseResources() {
final ReactiveDataProducer p = responseProducer.get();
if (p != null) {
p.releaseResources();
}
requestConsumer.releaseResources();
}
}
httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java 0100664 0000000 0000000 00000013477 14156462653 030134 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpStreamResetException;
import org.apache.hc.core5.http.nio.AsyncDataProducer;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import org.apache.hc.core5.util.Args;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
/**
* An asynchronous data producer that supports Reactive Streams.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.SAFE)
final class ReactiveDataProducer implements AsyncDataProducer, Subscriber {
private static final int BUFFER_WINDOW_SIZE = 5;
private final AtomicReference requestChannel = new AtomicReference<>();
private final AtomicReference exception = new AtomicReference<>();
private final AtomicBoolean complete = new AtomicBoolean(false);
private final Publisher publisher;
private final AtomicReference subscription = new AtomicReference<>();
private final ArrayDeque buffers = new ArrayDeque<>(); // This field requires synchronization
public ReactiveDataProducer(final Publisher publisher) {
this.publisher = Args.notNull(publisher, "publisher");
}
void setChannel(final DataStreamChannel channel) {
requestChannel.set(channel);
}
@Override
public void onSubscribe(final Subscription subscription) {
if (this.subscription.getAndSet(subscription) != null) {
throw new IllegalStateException("Already subscribed");
}
subscription.request(BUFFER_WINDOW_SIZE);
}
@Override
public void onNext(final ByteBuffer byteBuffer) {
final byte[] copy = new byte[byteBuffer.remaining()];
byteBuffer.get(copy);
synchronized (buffers) {
buffers.add(ByteBuffer.wrap(copy));
}
signalReadiness();
}
@Override
public void onError(final Throwable throwable) {
subscription.set(null);
exception.set(throwable);
signalReadiness();
}
@Override
public void onComplete() {
subscription.set(null);
complete.set(true);
signalReadiness();
}
private void signalReadiness() {
final DataStreamChannel channel = requestChannel.get();
if (channel == null) {
throw new IllegalStateException("Output channel is not set");
}
channel.requestOutput();
}
@Override
public int available() {
if (exception.get() != null || complete.get()) {
return 1;
} else {
synchronized (buffers) {
int sum = 0;
for (final ByteBuffer buffer : buffers) {
sum += buffer.remaining();
}
return sum;
}
}
}
@Override
public void produce(final DataStreamChannel channel) throws IOException {
if (requestChannel.get() == null) {
requestChannel.set(channel);
publisher.subscribe(this);
}
final Throwable t = exception.get();
final Subscription s = subscription.get();
int buffersToReplenish = 0;
try {
synchronized (buffers) {
if (t != null) {
throw new HttpStreamResetException(t.getMessage(), t);
} else if (this.complete.get() && buffers.isEmpty()) {
channel.endStream();
} else {
while (!buffers.isEmpty()) {
final ByteBuffer nextBuffer = buffers.remove();
channel.write(nextBuffer);
if (nextBuffer.remaining() > 0) {
buffers.push(nextBuffer);
break;
} else if (s != null) {
// We defer the #request call until after we release the buffer lock.
buffersToReplenish++;
}
}
}
}
} finally {
if (s != null && buffersToReplenish > 0) {
s.request(buffersToReplenish);
}
}
}
@Override
public void releaseResources() {
final Subscription s = subscription.getAndSet(null);
if (s != null) {
s.cancel();
}
}
}
httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java 0100644 0000000 0000000 00000007533 13354633361 030524 0 ustar 00 0000000 0000000 /*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.hc.core5.reactive;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import org.reactivestreams.Publisher;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Set;
/**
* An {@link AsyncEntityProducer} that subscribes to a {@code Publisher}
* instance, as defined by the Reactive Streams specification.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.SAFE)
public final class ReactiveEntityProducer implements AsyncEntityProducer {
private final ReactiveDataProducer reactiveDataProducer;
private final long contentLength;
private final ContentType contentType;
private final String contentEncoding;
/**
* Creates a new {@code ReactiveEntityProducer} with the given parameters.
*
* @param publisher the publisher of the entity stream.
* @param contentLength the length of the entity, or -1 if unknown (implies chunked encoding).
* @param contentType the {@code Content-Type} of the entity, or null if none.
* @param contentEncoding the {@code Content-Encoding} of the entity, or null if none.
*/
public ReactiveEntityProducer(
final Publisher publisher,
final long contentLength,
final ContentType contentType,
final String contentEncoding
) {
this.reactiveDataProducer = new ReactiveDataProducer(publisher);
this.contentLength = contentLength;
this.contentType = contentType;
this.contentEncoding = contentEncoding;
}
@Override
public int available() {
return reactiveDataProducer.available();
}
@Override
public void produce(final DataStreamChannel channel) throws IOException {
reactiveDataProducer.produce(channel);
}
@Override
public void releaseResources() {
reactiveDataProducer.releaseResources();
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public void failed(final Exception cause) {
releaseResources();
}
@Override
public long getContentLength() {
return contentLength;
}
@Override
public String getContentType() {
return contentType != null ? contentType.toString() : null;
}
@Override
public String getContentEncoding() {
return contentEncoding;
}
@Override
public boolean isChunked() {
return contentLength == -1;
}
@Override
public Set getTrailerNames() {
return null;
}
}
httpcore5-reactive/pom.xml 0100664 0000000 0000000 00000006037 14157710151 014651 0 ustar 00 0000000 0000000
httpcore5-parent
org.apache.httpcomponents.core5
5.1.3
4.0.0
httpcore5-reactive
Apache HttpComponents Core Reactive Extensions
Apache HttpComponents Reactive Streams Bindings
org.apache.httpcomponents.core5.httpcore5.reactive
org.apache.httpcomponents.core5
httpcore5
${project.version}
org.reactivestreams
reactive-streams
1.0.3
junit
junit
test
io.reactivex.rxjava2
rxjava
${rxjava.version}
test
maven-project-info-reports-plugin
false
index
dependencies
dependency-info
summary