diff --git a/pom.xml b/pom.xml index d1bc3b9..bb5ec03 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ 2005 pom + + 17 + 17 + + scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-website.git scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-website.git diff --git a/samples/pom.xml b/samples/pom.xml index ff0eea4..3930d36 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -38,12 +38,9 @@ jar - 1.8 - 1.8 4.5.14 - 5.6 - 2.20.1 - 0.4.0 + 5.5-beta1 + 5.6.1 @@ -57,6 +54,16 @@ fluent-hc ${hc4.client.version} + + org.apache.httpcomponents.core5 + httpcore5-jackson2 + ${hc5.core.version} + + + org.apache.httpcomponents.core5 + httpcore5-testing + ${hc5.core.version} + org.apache.httpcomponents.client5 httpclient5 @@ -67,16 +74,6 @@ httpclient5-fluent ${hc5.client.version} - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.github.ok2c.hc5 - hc5-async-json - ${asyncjson.version} - diff --git a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient4Example.java b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient4Example.java index 21221b6..a5ee654 100644 --- a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient4Example.java +++ b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient4Example.java @@ -51,7 +51,7 @@ public class HttpClient4Example { public static void main(String... args) throws Exception { CloseableHttpClient client = HttpClients.custom() .setSSLSocketFactory(new SSLConnectionSocketFactory( - SSLContexts.createSystemDefault(), + SSLContexts.createDefault(), new String[] { "TLSv1.2" }, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier())) diff --git a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncSimpleExample.java b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncSimpleExample.java index a55e1f7..f9c9123 100644 --- a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncSimpleExample.java +++ b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncSimpleExample.java @@ -59,7 +59,7 @@ public class HttpClient5AsyncSimpleExample { public static void main(String... args) throws Exception { PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) diff --git a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamExample.java b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamExample.java index 6881879..f977e5f 100644 --- a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamExample.java +++ b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamExample.java @@ -22,8 +22,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ok2c.hc5.json.http.JsonRequestProducers; -import com.ok2c.hc5.json.http.JsonResponseConsumers; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.config.ConnectionConfig; @@ -46,6 +44,8 @@ import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.jackson2.http.JsonRequestProducers; +import org.apache.hc.core5.jackson2.http.JsonResponseConsumers; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.reactor.IOReactorConfig; @@ -58,7 +58,7 @@ public class HttpClient5AsyncStreamExample { public static void main(String... args) throws Exception { PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) @@ -107,7 +107,7 @@ public static void main(String... args) throws Exception { new org.apache.http.message.BasicNameValuePair("name2", "value2")), objectMapper), JsonResponseConsumers.create(jsonFactory), - new FutureCallback>() { + new FutureCallback<>() { @Override public void completed(Message message) { diff --git a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamHttp2Example.java b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamHttp2Example.java index 0ecc35a..6332843 100644 --- a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamHttp2Example.java +++ b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5AsyncStreamHttp2Example.java @@ -16,15 +16,11 @@ package org.apache.hc.client5.migration.examples; -import java.util.Arrays; -import java.util.concurrent.Future; - import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ok2c.hc5.json.http.JsonRequestProducers; -import com.ok2c.hc5.json.http.JsonResponseConsumers; - +import java.util.Arrays; +import java.util.concurrent.Future; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; @@ -43,6 +39,8 @@ import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.jackson2.http.JsonRequestProducers; +import org.apache.hc.core5.jackson2.http.JsonResponseConsumers; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.TimeValue; @@ -53,7 +51,7 @@ public class HttpClient5AsyncStreamHttp2Example { public static void main(String... args) throws Exception { CloseableHttpAsyncClient client = HttpAsyncClients.customHttp2() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setIOReactorConfig(IOReactorConfig.custom() diff --git a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5ClassicExample.java b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5ClassicExample.java index 7dda82b..a96d1bd 100644 --- a/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5ClassicExample.java +++ b/samples/src/main/java/org/apache/hc/client5/migration/examples/HttpClient5ClassicExample.java @@ -58,7 +58,7 @@ public class HttpClient5ClassicExample { public static void main(String... args) throws Exception { PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() .setTlsSocketStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildClassic()) .setDefaultSocketConfig(SocketConfig.custom() diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterBootstrapExample.java b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterBootstrapExample.java new file mode 100644 index 0000000..514dea9 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterBootstrapExample.java @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.util.concurrent.CountDownLatch; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.jackson2.http.AsyncJsonClientPipeline; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; +import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; +import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; +import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; +import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; +import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; +import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; +import org.apache.hc.core5.util.Timeout; + +public class AsyncRequesterBootstrapExample { + + public static void main(final String[] args) throws Exception { + + final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) + + // Pluggable logging + .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) + .setStreamListener(LoggingH2StreamListener.INSTANCE) + .setConnPoolListener(LoggingConnPoolListener.INSTANCE) + .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) + .setExceptionCallback(LoggingExceptionCallback.INSTANCE) + .setIOSessionListener(LoggingIOSessionListener.INSTANCE) + .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) + + .create(); + requester.start(); + + final HttpHost target = new HttpHost("httpbin.org"); + final HttpCoreContext context = HttpCoreContext.create(); + + final ObjectMapper objectMapper = new ObjectMapper(); + + final CountDownLatch latch = new CountDownLatch(1); + requester.execute( + AsyncJsonClientPipeline.assemble(objectMapper) + .request() + .post(target, "/post") + .asObject(new BasicNameValuePair("name", "value")) + .response() + .asObject(RequestData.class) + .result(new FutureCallback<>() { + + @Override + public void completed(final Message m) { + final HttpResponse response = m.head(); + final RequestData requestData = m.body(); + System.out.println(target + "->" + response.getCode()); + System.out.println(requestData); + latch.countDown(); + } + + @Override + public void failed(final Exception ex) { + ex.printStackTrace(System.out); + latch.countDown(); + } + + @Override + public void cancelled() { + latch.countDown(); + } + + }) + .create(), + Timeout.ofMinutes(1), + context); + + latch.await(); + + requester.close(CloseMode.GRACEFUL); + } + +} diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterMultiplexingExample.java b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterMultiplexingExample.java new file mode 100644 index 0000000..92b0306 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncRequesterMultiplexingExample.java @@ -0,0 +1,136 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.nio.AsyncClientEndpoint; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.jackson2.http.AsyncJsonClientPipeline; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; +import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; +import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; +import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; +import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; +import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; +import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; +import org.apache.hc.core5.util.Timeout; + +public class AsyncRequesterMultiplexingExample { + + public static void main(final String[] args) throws Exception { + + final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) + + // Pluggable logging + .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) + .setStreamListener(LoggingH2StreamListener.INSTANCE) + .setConnPoolListener(LoggingConnPoolListener.INSTANCE) + .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) + .setExceptionCallback(LoggingExceptionCallback.INSTANCE) + .setIOSessionListener(LoggingIOSessionListener.INSTANCE) + .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) + + .create(); + requester.start(); + + final HttpHost target = new HttpHost("nghttp2.org"); + + final ObjectMapper objectMapper = new ObjectMapper(); + + final Future future = requester.connect(target, Timeout.ofSeconds(30)); + final AsyncClientEndpoint clientEndpoint = future.get(); + + int n = 3; + final CountDownLatch latch = new CountDownLatch(n); + final AtomicBoolean failure = new AtomicBoolean(); + for (int i = 1; i <= 3; i++) { + requester.execute( + AsyncJsonClientPipeline.assemble(objectMapper) + .request() + .post(target, "/post") + .asObject(new BasicNameValuePair("name", "value")) + .response() + .asObject(RequestData.class) + .result(new FutureCallback<>() { + + @Override + public void completed(final Message m) { + final HttpResponse response = m.head(); + final RequestData requestData = m.body(); + System.out.println(target + "->" + response.getCode()); + System.out.println(requestData); + latch.countDown(); + } + + @Override + public void failed(final Exception ex) { + ex.printStackTrace(System.out); + failure.set(true); + latch.countDown(); + } + + @Override + public void cancelled() { + latch.countDown(); + } + + }) + .create(), + Timeout.ofMinutes(1), + HttpCoreContext.create()); + } + + latch.await(); + if (failure.get()) { + clientEndpoint.releaseAndDiscard(); + } else { + clientEndpoint.releaseAndReuse(); + } + + requester.close(CloseMode.GRACEFUL); + } + +} diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncServerBootstrapExample.java b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncServerBootstrapExample.java new file mode 100644 index 0000000..9051a01 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/AsyncServerBootstrapExample.java @@ -0,0 +1,170 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.net.InetSocketAddress; +import java.net.URISyntaxException; +import java.util.Objects; +import java.util.concurrent.Future; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.EndpointDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.Method; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; +import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; +import org.apache.hc.core5.http.impl.routing.RequestRouter; +import org.apache.hc.core5.http.message.BasicHttpResponse; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; +import org.apache.hc.core5.jackson2.http.AsyncJsonServerPipeline; +import org.apache.hc.core5.net.URIAuthority; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.reactor.ListenerEndpoint; +import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; +import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; +import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; +import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; +import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; +import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; + +public class AsyncServerBootstrapExample { + + /** + * Example command line args: {@code 8080} + */ + public static void main(final String[] args) throws Exception { + int port = 8080; + if (args.length >= 1) { + port = Integer.parseInt(args[1]); + } + + final ObjectMapper objectMapper = new ObjectMapper(); + + final Supplier exchangeHandlerSupplier = AsyncJsonServerPipeline.assemble(objectMapper) + // Read GET / HEAD requests by consuming content stream as JSON nodes + .request(Method.GET, Method.HEAD, Method.POST, Method.PUT, Method.PATCH) + .asJsonNode() + // Write out responses by streaming out content of JSON object + .response() + .asObject(RequestData.class) + // Map exceptions to a response message + .errorMessage(Throwable::getMessage) + // Generate a response to a request + .handle((m, context) -> { + final HttpRequest request = m.head(); + final RequestData rd = new RequestData(); + try { + rd.setUrl(request.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI"); + } + rd.generateHeaders(request.getHeaders()); + rd.setJson(m.body()); + rd.setData(Objects.toString(m.error())); + + final HttpCoreContext coreContext = HttpCoreContext.cast(context); + final EndpointDetails endpointDetails = coreContext.getEndpointDetails(); + + final InetSocketAddress remoteAddress = (InetSocketAddress) endpointDetails.getRemoteAddress(); + rd.setOrigin(Objects.toString(remoteAddress.getAddress())); + + return Message.of(new BasicHttpResponse(HttpStatus.SC_OK), rd); + }) + .supplier(); + + final AbstractAsyncServerAuthFilter authFilter = new AbstractAsyncServerAuthFilter<>(true) { + + @Override + protected String parseChallengeResponse( + final String authorizationValue, final HttpContext context) throws HttpException { + // Parse the authorizaton header value into a custom token representation + return authorizationValue; + } + + @Override + protected boolean authenticate( + final String token, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Validate token and return true if the user has been autheticated + return "let me pass".equals(token); + } + + @Override + protected String generateChallenge( + final String token, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Generate a challenge in case the user has not been autheticated + return "who goes there?"; + } + + }; + + final HttpAsyncServer server = H2ServerBootstrap.bootstrap() + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", exchangeHandlerSupplier) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) + .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), authFilter) + + // Pluggable logging + .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER) + .setStreamListener(LoggingH2StreamListener.INSTANCE) + .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) + .setExceptionCallback(LoggingExceptionCallback.INSTANCE) + .setIOSessionListener(LoggingIOSessionListener.INSTANCE) + .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) + + .create(); + + server.start(); + final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); + final ListenerEndpoint listenerEndpoint = future.get(); + System.out.println("Listening on " + listenerEndpoint.getAddress()); + server.awaitShutdown(TimeValue.MAX_VALUE); + } + +} \ No newline at end of file diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicRequesterBootstrapExample.java b/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicRequesterBootstrapExample.java new file mode 100644 index 0000000..7189282 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicRequesterBootstrapExample.java @@ -0,0 +1,92 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.io.InputStream; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; +import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.io.entity.EntityTemplate; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.testing.classic.LoggingBHttpClientConnectionFactory; +import org.apache.hc.core5.testing.classic.LoggingHttp1StreamListener; +import org.apache.hc.core5.util.Timeout; + +public class ClassicRequesterBootstrapExample { + + public static void main(final String[] args) throws Exception { + final HttpRequester requester = RequesterBootstrap.bootstrap() + .setSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + + // Pluggable logging + .setConnectionFactory(LoggingBHttpClientConnectionFactory.INSTANCE) + .setStreamListener(LoggingHttp1StreamListener.INSTANCE) + + .create(); + + final HttpHost target = new HttpHost("httpbin.org"); + final HttpCoreContext context = HttpCoreContext.create(); + + final ObjectMapper objectMapper = new ObjectMapper(); + + final ClassicHttpRequest request = ClassicRequestBuilder.get() + .setHttpHost(target) + .setEntity(new EntityTemplate(ContentType.APPLICATION_JSON, out -> { + objectMapper.writeValue(out, new BasicNameValuePair("name", "value")); + })) + .setPath("/post") + .build(); + final RequestData requestData = requester.execute(target, request, Timeout.ofSeconds(5), context, response -> { + System.out.println(target + "->" + response.getCode()); + final HttpEntity entity = response.getEntity(); + if (entity != null) { + try (InputStream inputStream = entity.getContent()) { + return objectMapper.readValue(inputStream, RequestData.class); + } + } else { + return null; + } + }); + System.out.println(requestData); + + requester.close(CloseMode.GRACEFUL); + } + +} diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicServerBootstrapExample.java b/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicServerBootstrapExample.java new file mode 100644 index 0000000..543ca55 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/ClassicServerBootstrapExample.java @@ -0,0 +1,163 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; +import java.util.Objects; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EndpointDetails; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.Method; +import org.apache.hc.core5.http.MethodNotSupportedException; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; +import org.apache.hc.core5.http.impl.routing.RequestRouter; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.io.entity.EntityTemplate; +import org.apache.hc.core5.http.io.support.AbstractHttpServerAuthFilter; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.net.URIAuthority; +import org.apache.hc.core5.testing.classic.LoggingBHttpServerConnectionFactory; +import org.apache.hc.core5.testing.classic.LoggingExceptionListener; +import org.apache.hc.core5.testing.classic.LoggingHttp1StreamListener; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; + +/** + * Example of embedded HTTP/1.1 file server using classic I/O. + */ +public class ClassicServerBootstrapExample { + + public static void main(final String[] args) throws Exception { + int port = 8080; + if (args.length >= 1) { + port = Integer.parseInt(args[1]); + } + + final ObjectMapper objectMapper = new ObjectMapper(); + + final HttpRequestHandler requestHandler = (request, response, localContext) -> { + + final String method = request.getMethod(); + if (!Method.GET.isSame(method) && + !Method.HEAD.isSame(method) && + !Method.POST.isSame(method) && + !Method.PUT.isSame(method) && + !Method.PATCH.isSame(method)) { + throw new MethodNotSupportedException(method + " method not supported"); + } + + final RequestData rd = new RequestData(); + try { + rd.setUrl(request.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI"); + } + rd.generateHeaders(request.getHeaders()); + final HttpEntity requestEntity = request.getEntity(); + if (requestEntity != null) { + try (final InputStream inputStream = requestEntity.getContent()) { + rd.setJson(objectMapper.readTree(inputStream)); + } + } + + final HttpCoreContext coreContext = HttpCoreContext.cast(localContext); + final EndpointDetails endpointDetails = coreContext.getEndpointDetails(); + + final InetSocketAddress remoteAddress = (InetSocketAddress) endpointDetails.getRemoteAddress(); + rd.setOrigin(Objects.toString(remoteAddress.getAddress())); + response.setEntity(new EntityTemplate(ContentType.APPLICATION_JSON, out -> { + objectMapper.writeValue(out, rd); + })); + }; + + final AbstractHttpServerAuthFilter authFilter = new AbstractHttpServerAuthFilter<>(false) { + + @Override + protected String parseChallengeResponse( + final String authorizationValue, final HttpContext context) throws HttpException { + // Parse the authorizaton header value into a custom token representation + return authorizationValue; + } + + @Override + protected boolean authenticate( + final String token, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Validate token and return true if the user has been autheticated + return "let me pass".equals(token); + } + + @Override + protected String generateChallenge( + final String challengeResponse, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Generate a challenge in case the user has not been autheticated + return "who goes there?"; + } + + }; + + final HttpServer server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) + .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), authFilter) + + // Pluggable logging + .setConnectionFactory(LoggingBHttpServerConnectionFactory.INSTANCE) + .setStreamListener(LoggingHttp1StreamListener.INSTANCE) + .setExceptionListener(LoggingExceptionListener.INSTANCE) + + .create(); + + server.start(); + System.out.println("Listening on port " + port); + server.awaitTermination(TimeValue.MAX_VALUE); + } + +} diff --git a/samples/src/main/java/org/apache/hc/core5/http/examples/RequestData.java b/samples/src/main/java/org/apache/hc/core5/http/examples/RequestData.java new file mode 100644 index 0000000..11e95d5 --- /dev/null +++ b/samples/src/main/java/org/apache/hc/core5/http/examples/RequestData.java @@ -0,0 +1,123 @@ +/* + * ==================================================================== + * 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.http.examples; + +import java.net.URI; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.NameValuePair; + +public class RequestData { + + private int id; + private URI url; + private String origin; + private Map headers; + private Map args; + private String data; + private JsonNode json; + + public int getId() { + return id; + } + + public void setId(final int id) { + this.id = id; + } + + public URI getUrl() { + return url; + } + + public void setUrl(final URI url) { + this.url = url; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(final String origin) { + this.origin = origin; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(final Map headers) { + this.headers = headers; + } + + public void generateHeaders(final Header... headers) { + this.headers = Arrays.stream(headers) + .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); + } + + public Map getArgs() { + return args; + } + + public void setArgs(final Map args) { + this.args = args; + } + + public String getData() { + return data; + } + + public void setData(final String data) { + this.data = data; + } + + public JsonNode getJson() { + return json; + } + + public void setJson(final JsonNode json) { + this.json = json; + } + + @Override + public String toString() { + return "RequestData{" + + "id=" + id + + ", url=" + url + + ", origin='" + origin + '\'' + + ", headers=" + headers + + ", args=" + args + + ", data='" + data + '\'' + + ", json=" + json + + '}'; + } + +} diff --git a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-http2.md b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-http2.md index c8fccde..a1c17bb 100644 --- a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-http2.md +++ b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-http2.md @@ -37,7 +37,7 @@ over a single HTTP/2 connection. ```java CloseableHttpAsyncClient client = HttpAsyncClients.customHttp2() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setIOReactorConfig(IOReactorConfig.custom() diff --git a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-simple.md b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-simple.md index e393342..60dc0c7 100644 --- a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-simple.md +++ b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-simple.md @@ -43,7 +43,7 @@ either known to be well-behaved or specifically designed for simple message hand ```java PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) diff --git a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-streaming.md b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-streaming.md index 1733c25..f38d697 100644 --- a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-streaming.md +++ b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-async-streaming.md @@ -64,7 +64,7 @@ for [JSON message processing](https://github.com/ok2c/httpcomponents-jackson) us ```java PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildAsync()) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) diff --git a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-classic.md b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-classic.md index 4e85539..fe51dba 100644 --- a/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-classic.md +++ b/src/site/markdown/httpcomponents-client-5.6.x/migration-guide/migration-to-classic.md @@ -63,7 +63,7 @@ There are also some important differences with URL normalization and encoding. ```java PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() .setTlsSocketStrategy(ClientTlsStrategyBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) + .setSslContext(SSLContexts.createDefault()) .setTlsVersions(TLS.V_1_3) .buildClassic()) .setDefaultSocketConfig(SocketConfig.custom() diff --git a/src/site/markdown/httpcomponents-client-5.6.x/spki-pinning.md b/src/site/markdown/httpcomponents-client-5.6.x/spki-pinning.md index eb2352b..f4420a8 100644 --- a/src/site/markdown/httpcomponents-client-5.6.x/spki-pinning.md +++ b/src/site/markdown/httpcomponents-client-5.6.x/spki-pinning.md @@ -112,7 +112,7 @@ import org.apache.hc.core5.ssl.SSLContexts; public final class ClientSpkiPinningExample { public static void main(final String[] args) throws Exception { - final SSLContext sslContext = SSLContexts.createSystemDefault(); + final SSLContext sslContext = SSLContexts.createDefault(); final SpkiPinningClientTlsStrategy pinning = SpkiPinningClientTlsStrategy .newBuilder(sslContext) @@ -179,7 +179,7 @@ import org.apache.hc.core5.ssl.SSLContexts; public final class AsyncSpkiPinningExample { public static void main(final String[] args) throws Exception { - final SSLContext sslContext = SSLContexts.createSystemDefault(); + final SSLContext sslContext = SSLContexts.createDefault(); final SpkiPinningClientTlsStrategy pinning = SpkiPinningClientTlsStrategy .newBuilder(sslContext) diff --git a/src/site/markdown/httpcomponents-core-5.5.x/getting-started.md b/src/site/markdown/httpcomponents-core-5.5.x/getting-started.md new file mode 100644 index 0000000..2747145 --- /dev/null +++ b/src/site/markdown/httpcomponents-core-5.5.x/getting-started.md @@ -0,0 +1,590 @@ +Getting started with HttpCore +================= + +Please note HttpCore is a set of customizable components, not an embeddable server or a +client. It is not intended for deployment to productive environments, especially +potentially hostile, without prior customization and configuration. + +Supported I/O models and HTTP protocol versions +------------------ + +HttpCore supports two i/o models: the classic i/o model based on blocking InputStream / +OutputStream APIs and the event-driven async i/o model. Both models have their advantages +and their special use cases. + +The async i/o model is believed to be more scalable and better suited for working with +thousands of concurrent connections while utilizing just a few reactor threads. This can +especially be relevant if many of connections can remain idle for a considerable period +of time. However, this may no longer be the case for newer Java Runtimes that support +virtual threads. + +The classic i/o model tends to be simpler and more convenient due to its seamless +integration with any InputStream / OutputStream based content producer and consumer +and much more straight-forward exception handling. + +The async i/o model on the other hand tends to be more complex due to its event-driven +nature. It also does not work well with InputStream / OutputStream based content +producers and consumers and usually may still need to maintain a worker thread if the +message exchange handler can get blocked while producing or consuming data. + +The classic i/o model requires a dedicated thread for each active connection in order +to be able to read or write data. But it cannot do both reading and writing while using +a single thread. This makes the classic i/o model ill-suited for multiplexing protocols +such as HTTP/2. However, it works very well with request / response based protocols such +as HTTP/1.1. + +Ultimately the choice of an i/o model boils down to whether or not an application can +make an effective use of message exchange multiplexing with multiple long message streams +running concurrently over the same physical connection. If so, one would be better off +choosing the async (event-driven) model. Otherwise, one may choose the classic i/o model +as in many common use cases HTTP/2 provides no tangible advantage over HTTP/1.1, +especially if the application is based on the REST request / response style of +communicaton. + +HTTP requesters +------------------ +HttpCore ships with several HTTP requester implementations. Please note that HttpCore +requesters are not full-featured clients. They can efficiently execute HTTP message +exchanges over multiple pooled connections but do not support advanced client features +such as automatic redirect handling, content compression / decompression, response +caching, state management and different authentication schemes and credentials management. +For advanced client features one should use Apache HttpClient instead. + +HttpCore requesters can however be efficiently used to execute JSON formatted message +exchanges with remote REST endpoints using a Bearer tokenor similar for authetication. + +### Async requester + +The async requester can efficiently execute multiple message exchanges over thousands of +concurrent connections using HTTP/1.1 and HTTP/2 protocol. + +Please note async message exchange handlers are stateful and cannot be executed multiple +times (unless their state can be reset). One must create a new instance for each new +message exchange execution. + +One can use`AsyncClientPipeline` and `AsyncJsonClientPipeline` utility classes to +simplify assembly of async message exchange handlers. + +```java +final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) + .create(); +requester. + +start(); + +final HttpHost target = new HttpHost("httpbin.org"); +final HttpCoreContext context = HttpCoreContext.create(); + +final ObjectMapper objectMapper = new ObjectMapper(); + +final CountDownLatch latch = new CountDownLatch(1); +requester. + +execute( + AsyncJsonClientPipeline.assemble(objectMapper) + . + +request() + . + +post(target, "/post") + . + +asObject(new BasicNameValuePair("name", "value")) + . + +response() + . + +asObject(RequestData .class) + . + +result(new FutureCallback<>() { + + @Override + public void completed ( final Message m){ + final HttpResponse response = m.head(); + System.out.println(target + "->" + response.getCode()); + latch.countDown(); + } + + @Override + public void failed ( final Exception ex){ + ex.printStackTrace(System.out); + latch.countDown(); + } + + @Override + public void cancelled () { + latch.countDown(); + } + +}) + . + +create(), + Timeout. + +ofMinutes(1), + +context); + + latch. + +await(); +requester. + +close(CloseMode.GRACEFUL); +``` + +### Classic requester + +The classic transport generally tends to be more convenient to use and integrate with +content libraries based on standard `InputStream`/`OutputStream` APIs. One should consider +choosing the classic requester for use scenarios with the HTTP/2 protocol support being +optional. + +Classic message exchange handlers can be stateless and they generally tend to have +a simpler, more predictable execution flow and exception handling. + +```java +final HttpRequester requester = RequesterBootstrap.bootstrap() + .setSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .create(); + +final HttpHost target = new HttpHost("httpbin.org"); +final HttpCoreContext context = HttpCoreContext.create(); + +final ObjectMapper objectMapper = new ObjectMapper(); + +final ClassicHttpRequest request = ClassicRequestBuilder.get() + .setHttpHost(target) + .setEntity(new EntityTemplate(ContentType.APPLICATION_JSON, out -> { + objectMapper.writeValue(out, new BasicNameValuePair("name", "value")); + })) + .setPath("/post") + .build(); +final RequestData requestData = requester.execute(target, request, Timeout.ofSeconds(5), context, response -> { + System.out.println(target + "->" + response.getCode()); + final HttpEntity entity = response.getEntity(); + if (entity != null) { + try (InputStream inputStream = entity.getContent()) { + return objectMapper.readValue(inputStream, RequestData.class); + } + } else { + return null; + } +}); +System.out. + +println(requestData); + +requester. + +close(CloseMode.GRACEFUL); +``` + +HTTP servers +------------------ +HttpCore ships with several HTTP embeddable server implementations. The servers are fully +capable of efficiently managing multiple concurrent message exchanges over multiple +connections. Incoming requrests can be routed to individual request handlers for +processing based on predefined request route definitions. + +Please note however HttpCore servers do not support advanced server features such as +different authentication schemes, access control and user identity management out of the +box. These features however can be plugged in using custom message exchange interceptors. + +HttpCore servers can be efficiently used to handle JSON formatted message exchanges with +remote REST client using Bearer token scheme or similar for authentication. + +### Async server + +The async server can efficiently execute multiple message exchanges over thousands of +concurrent connections using HTTP/1.1 and HTTP/2 protocol. + +Please note async message exchange handlers are state-ful and cannot be executed multiple +times. One must create a new instance for each new message exchange execution. + +One can use`AsyncServerPipeline` and `AsyncJsonServerPipeline` utility classes to +simplify assembly of async message exchange handlers. + +```java +final IOReactorConfig config = IOReactorConfig.custom() + .setSoTimeout(15, TimeUnit.SECONDS) + .build(); + +final ObjectMapper objectMapper = new ObjectMapper(); + +final Supplier exchangeHandlerSupplier = AsyncJsonServerPipeline.assemble(objectMapper) + // Read GET / HEAD requests by consuming content stream as JSON nodes + .request(Method.GET, Method.HEAD, Method.POST, Method.PUT, Method.PATCH) + .asJsonNode() + // Write out responses by streaming out content of JSON object + .response() + .asObject(RequestData.class) + // Map exceptions to a response message + .errorMessage(Throwable::getMessage) + // Generate a response to a request + .handle((m, context) -> { + final HttpRequest request = m.head(); + final RequestData rd = new RequestData(); + try { + rd.setUrl(request.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI"); + } + rd.generateHeaders(request.getHeaders()); + rd.setJson(m.body()); + rd.setData(Objects.toString(m.error())); + + final HttpCoreContext coreContext = HttpCoreContext.cast(context); + final EndpointDetails endpointDetails = coreContext.getEndpointDetails(); + + final InetSocketAddress remoteAddress = (InetSocketAddress) endpointDetails.getRemoteAddress(); + rd.setOrigin(Objects.toString(remoteAddress.getAddress())); + + return Message.of(new BasicHttpResponse(HttpStatus.SC_OK), rd); + }) + .supplier(); + +final AbstractAsyncServerAuthFilter authFilter = new AbstractAsyncServerAuthFilter<>(true) { + + @Override + protected String parseChallengeResponse( + final String authorizationValue, final HttpContext context) throws HttpException { + // Parse the authorizaton header value into a custom token representation + return authorizationValue; + } + + @Override + protected boolean authenticate( + final String token, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Validate token and return true if the user has been autheticated + return "let me pass".equals(challengeResponse); + } + + @Override + protected String generateChallenge( + final String challengeResponse, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Generate a challenge in case the user has not been autheticated + return "who goes there?"; + } + +}; +final HttpAsyncServer server = H2ServerBootstrap.bootstrap() + .setExceptionCallback(e -> e.printStackTrace()) + .setIOReactorConfig(config) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", exchangeHandlerSupplier) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) + .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), authFilter) + .create(); + + +server. + +start(); + +final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); +final ListenerEndpoint listenerEndpoint = future.get(); +System.out. + +println("Listening on "+listenerEndpoint.getAddress()); + server. + +awaitShutdown(TimeValue.MAX_VALUE); +``` + +### Classic server + +The classic server can be a good choice when one controls both the client and the server +sides, does not need to handle requests with large or unexpected content body and support +thousands of concurrent connections. + +Classic message exchange handlers can be stateless and they generally tend to have a +simpler, more predictable execution flow and exception handling. + +```java +final ObjectMapper objectMapper = new ObjectMapper(); + +final HttpRequestHandler requestHandler = (request, response, localContext) -> { + + final String method = request.getMethod(); + if (!Method.GET.isSame(method) && + !Method.HEAD.isSame(method) && + !Method.POST.isSame(method) && + !Method.PUT.isSame(method) && + !Method.PATCH.isSame(method)) { + throw new MethodNotSupportedException(method + " method not supported"); + } + + final RequestData rd = new RequestData(); + try { + rd.setUrl(request.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI"); + } + rd.generateHeaders(request.getHeaders()); + final HttpEntity requestEntity = request.getEntity(); + if (requestEntity != null) { + try (final InputStream inputStream = requestEntity.getContent()) { + rd.setJson(objectMapper.readTree(inputStream)); + } + } + + final HttpCoreContext coreContext = HttpCoreContext.cast(localContext); + final EndpointDetails endpointDetails = coreContext.getEndpointDetails(); + + final InetSocketAddress remoteAddress = (InetSocketAddress) endpointDetails.getRemoteAddress(); + rd.setOrigin(Objects.toString(remoteAddress.getAddress())); + response.setEntity(new EntityTemplate(ContentType.APPLICATION_JSON, out -> { + objectMapper.writeValue(out, rd); + })); +}; + +final AbstractHttpServerAuthFilter authFilter = new AbstractHttpServerAuthFilter<>(false) { + + @Override + protected String parseChallengeResponse( + final String authorizationValue, final HttpContext context) throws HttpException { + // Parse the authorizaton header value into a custom token representation + return authorizationValue; + } + + @Override + protected boolean authenticate( + final String token, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Validate token and return true if the user has been autheticated + return "let me pass".equals(token); + } + + @Override + protected String generateChallenge( + final String challengeResponse, + final URIAuthority authority, + final String requestUri, + final HttpContext context) { + // Generate a challenge in case the user has not been autheticated + return "who goes there?"; + } + +}; + +final HttpServer server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) + .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), authFilter) + .create(); + +server. + +start(); +System.out. + +println("Listening on port "+port); +server. + +awaitTermination(TimeValue.MAX_VALUE); +``` + +Request multiplexing / pipelining +------------------ + +HttpCore supports message exchange multiplexing over HTTP/2 connections and message +exchange pipelining over HTTP/1.1 connections by default. However initiation of concurrent +message exchanges on the client side require some manual management of the connection +lease. +One needs to lease a connection from the connection pool to execute multiple messages +over the same connection, and release it back to the pool when done. One also must +aqcuire a new connection from the connection in case a message exchange failure due to +an I/O error. + +```java +final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofMinutes(1)) + .build()) + .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) + .create(); +requester. + +start(); + +final HttpHost target = new HttpHost("nghttp2.org"); + +final ObjectMapper objectMapper = new ObjectMapper(); + +final Future future = requester.connect(target, Timeout.ofSeconds(30)); +final AsyncClientEndpoint clientEndpoint = future.get(); + +int n = 3; +final CountDownLatch latch = new CountDownLatch(n); +final AtomicBoolean failure = new AtomicBoolean(); +for( +int i = 1; +i <=3;i++){ + requester. + +execute( + AsyncJsonClientPipeline.assemble(objectMapper) + . + +request() + . + +post(target, "/post") + . + +asObject(new BasicNameValuePair("name", "value")) + . + +response() + . + +asObject(RequestData .class) + . + +result(new FutureCallback<>() { + + @Override + public void completed ( final Message m){ + final HttpResponse response = m.head(); + final RequestData requestData = m.body(); + System.out.println(target + "->" + response.getCode()); + System.out.println(requestData); + latch.countDown(); + } + + @Override + public void failed ( final Exception ex){ + ex.printStackTrace(System.out); + failure.set(true); + latch.countDown(); + } + + @Override + public void cancelled () { + latch.countDown(); + } + +}) + . + +create(), + Timeout. + +ofMinutes(1), + HttpCoreContext. + +create()); + } + + latch. + +await(); +if(failure. + +get()){ + clientEndpoint. + +releaseAndDiscard(); +}else{ + clientEndpoint. + +releaseAndReuse(); +} + + requester. + +close(CloseMode.GRACEFUL); +``` + +Logging +------------------ + +HttpCore does not depend on a specific logging toolkit or logging facade. This is a +conscious decision as HttpCore has no direct dependencies on any other libraties by +design. + +HttpCore requesters and servers however expose several listener interfaces that can +be used to plug-in custom logging. + +HttpCore ships with `httpcore5-testing` module that provides a number of listener +implementations that log DEBUG level priority events through `slf4j` facade. + +* Async server logging + +```java +final HttpAsyncServer server = H2ServerBootstrap.bootstrap() + // Pluggable logging + .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER) + .setStreamListener(LoggingH2StreamListener.INSTANCE) + .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) + .setExceptionCallback(LoggingExceptionCallback.INSTANCE) + .setIOSessionListener(LoggingIOSessionListener.INSTANCE) + .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) + + .create(); +``` + +* Classic server logging + +```java +final HttpServer server = ServerBootstrap.bootstrap() + // Pluggable logging + .setConnectionFactory(LoggingBHttpServerConnectionFactory.INSTANCE) + .setStreamListener(LoggingHttp1StreamListener.INSTANCE) + .setExceptionListener(LoggingExceptionListener.INSTANCE) + + .create(); +``` + +* Async requester logging + +```java +final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() + // Pluggable logging + .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) + .setStreamListener(LoggingH2StreamListener.INSTANCE) + .setConnPoolListener(LoggingConnPoolListener.INSTANCE) + .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) + .setExceptionCallback(LoggingExceptionCallback.INSTANCE) + .setIOSessionListener(LoggingIOSessionListener.INSTANCE) + .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) + + .create(); +``` + +* Classic requester logging + +```java +final HttpRequester requester = RequesterBootstrap.bootstrap() + // Pluggable logging + .setConnectionFactory(LoggingBHttpClientConnectionFactory.INSTANCE) + .setStreamListener(LoggingHttp1StreamListener.INSTANCE) + + .create(); +``` diff --git a/src/site/markdown/httpcomponents-core-5.5.x/index.md b/src/site/markdown/httpcomponents-core-5.5.x/index.md index c20730e..ffdb1d9 100644 --- a/src/site/markdown/httpcomponents-core-5.5.x/index.md +++ b/src/site/markdown/httpcomponents-core-5.5.x/index.md @@ -20,35 +20,53 @@ HttpCore Overview ================= -HttpCore is a set of low level HTTP transport components that can be used to build custom client and server side HTTP -services with a minimal footprint. HttpCore supports two I/O models: blocking I/O model based on the classic Java I/O -and non-blocking, event driven I/O model based on Java NIO. +HttpCore is a set of low level HTTP transport components that can be used to build custom +client and server side HTTP services with a minimal footprint. HttpCore supports two I/O +models: blocking I/O model based on the classic Java I/O and non-blocking, event driven +I/O model based on Java NIO. + +Design objectives and project scope +----------------- + +* Small footprint with no mandatory dependencies beyond JRE +* Self-sufficient HTTP transport impmementation capable of executing message exchanges + from any endpoint in the network topology (client, server, proxy) +* Common message primitives, control interfaces and strategies shared by the client and + server side protocol implementations +* Consistent APIs for all supported HTTP protocol versions +* HTTP protocol logic can be executed and tested against itself on the opposite endpoint +* A foundation for Apache HttpClient +* Potentially a foundation for an embeddable server similar in scope to Apache HttpClient Documentation ============= +1. Guides + - [Getting started](getting-started.md) + - [JSON processing](json-jackson2.md) + 1. Examples demonstrating some common as well as more complex use cases - - [HttpCore Cclassic APIs](examples.md) - - [HttpCore Async APIs](examples-async.md) - - [HttpCore Reactive Streams APIs](examples-reactive.md) - - [HttpCore JSON bindings](examples-jackson2.md) + - [HttpCore Cclassic APIs](examples.md) + - [HttpCore Async APIs](examples-async.md) + - [HttpCore Reactive Streams APIs](examples-reactive.md) + - [HttpCore JSON bindings](examples-jackson2.md) 1. Javadocs - - [HttpCore HTTP/1.1](./current/httpcore5/apidocs/) - - [HttpCore HTTP/2](./current/httpcore5-h2/apidocs/) - - [HttpCore Reactive Streams](./current/httpcore5-reactive/apidocs/) - - [HttpCore JSON bindings](./current/httpcore5-jackson2/apidocs/) + - [HttpCore HTTP/1.1](./current/httpcore5/apidocs/) + - [HttpCore HTTP/2](./current/httpcore5-h2/apidocs/) + - [HttpCore Reactive Streams](./current/httpcore5-reactive/apidocs/) + - [HttpCore JSON bindings](./current/httpcore5-jackson2/apidocs/) 1. API compatibility reports - - [HttpCore HTTP/1.1](./current/httpcore5/japicmp.html) - - [HttpCore HTTP/2](./current/httpcore5-h2/japicmp.html) - - [HttpCore Reactive Streams](./current/httpcore5-reactive/japicmp.html) + - [HttpCore HTTP/1.1](./current/httpcore5/japicmp.html) + - [HttpCore HTTP/2](./current/httpcore5-h2/japicmp.html) + - [HttpCore Reactive Streams](./current/httpcore5-reactive/japicmp.html) Standards Compliance -------------------- -HttpCore components strive to conform to the following specifications endorsed by the Internet Engineering Task Force -(IETF) and the internet at large: +HttpCore components strive to conform to the following specifications endorsed by the +Internet Engineering Task Force (IETF) and the internet at large: - [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110) - HTTP Semantics - [RFC 9112](https://datatracker.ietf.org/doc/html/rfc9112) - Hypertext Transfer Protocol Version 1.1 (HTTP/1.1) diff --git a/src/site/markdown/httpcomponents-core-5.5.x/json-jackson2.md b/src/site/markdown/httpcomponents-core-5.5.x/json-jackson2.md index 5ec05f8..188ce6f 100644 --- a/src/site/markdown/httpcomponents-core-5.5.x/json-jackson2.md +++ b/src/site/markdown/httpcomponents-core-5.5.x/json-jackson2.md @@ -37,4 +37,4 @@ content and map onto a higher level Java object model. Presently HttpCore JSON bindings use version 2 of Jackson JSON processor. -Some examples of HttpCore JSON bindings in action cab be found [here](examples-jackson2.md) \ No newline at end of file +Some examples of HttpCore JSON bindings in action can be found [here](examples-jackson2.md) \ No newline at end of file diff --git a/src/site/site.xml b/src/site/site.xml index 69e3cb2..6e98a35 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -99,6 +99,7 @@ under the License. +