| <?xml version="1.0" encoding="UTF-8"?> | |
| <!-- | |
| ~ 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. | |
| --> | |
| <document xmlns="http://maven.apache.org/XDOC/2.0" | |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd"> | |
| <properties> | |
| <title>HTTP/2 Java Client — Sample Code</title> | |
| </properties> | |
| <body> | |
| <section name="HTTP/2 Java Client — Sample Code"> | |
| <p><strong>What this is:</strong> A standalone sample client | |
| (<code>Http2JsonClient</code>) that demonstrates how to call Axis2 | |
| JSON-RPC services over HTTP/2 from plain Java using Apache HttpClient 5. | |
| It is <em>not</em> part of the Axis2 framework — it is example code | |
| in the userguide samples that you can copy and adapt for your own | |
| project.</p> | |
| <p><strong>Why it exists:</strong> Java's built-in | |
| <code>HttpURLConnection</code> does not support HTTP/2. Apache | |
| HttpClient 5's convenience classes (<code>SimpleHttpRequest</code> / | |
| <code>SimpleHttpResponse</code>) support HTTP/2 but silently buffer | |
| the entire response in memory, defeating the streaming benefit. | |
| This sample shows the correct pattern — using | |
| <code>AbstractBinResponseConsumer</code> with the async API — so | |
| you don't have to rediscover it the hard way.</p> | |
| <p>Two execution modes:</p> | |
| <ul> | |
| <li><strong>Buffered</strong> — returns the full response as a | |
| <code>String</code>. Simple, suitable for responses that fit | |
| in memory.</li> | |
| <li><strong>Streaming</strong> — writes response bytes to an | |
| <code>OutputStream</code> in 64KB chunks as HTTP/2 DATA | |
| frames arrive. Memory stays flat regardless of response size. | |
| When paired with the | |
| <a href="json-streaming-formatter.html">Streaming JSON | |
| Formatter</a> (AXIS2-6103), data flows end-to-end in 64KB | |
| chunks.</li> | |
| </ul> | |
| </section> | |
| <section name="The SimpleHttp* Pitfall"> | |
| <p>Apache HttpClient 5 provides <code>SimpleHttpRequest</code> and | |
| <code>SimpleHttpResponse</code> as convenience classes for async | |
| requests. <strong>Do not use them for HTTP/2 workloads with large | |
| responses.</strong> They appear to work, but they silently defeat | |
| HTTP/2 streaming.</p> | |
| <p><code>SimpleHttpResponse</code> is a buffering response object — | |
| it accumulates the entire response body in memory before returning | |
| it to the caller. For a 100MB response:</p> | |
| <ul> | |
| <li><code>SimpleHttpResponse</code>: allocates 100MB+ of heap | |
| (internal byte arrays, header maps, content type parsing) | |
| before your code sees a single byte</li> | |
| <li><code>AbstractBinResponseConsumer</code>: your | |
| <code>data(ByteBuffer)</code> callback fires for each 64KB | |
| HTTP/2 DATA frame — memory stays flat at ~64KB working | |
| set</li> | |
| </ul> | |
| <p>This is not obvious from the HttpClient 5 documentation, and | |
| it is easy to write code that uses <code>SimpleHttpResponse</code>, | |
| observes correct HTTP/2 ALPN negotiation in the logs, and concludes | |
| that HTTP/2 streaming is working — when in fact the response is | |
| fully buffered before your code runs. The sample client avoids this | |
| by using <code>AbstractBinResponseConsumer</code> for all requests, | |
| including the buffered convenience method.</p> | |
| </section> | |
| <section name="Dependencies"> | |
| <p>Requires Java 11+ (ALPN built in) and Apache HttpClient 5.4+:</p> | |
| <source> | |
| <!-- Maven --> | |
| <dependency> | |
| <groupId>org.apache.httpcomponents.client5</groupId> | |
| <artifactId>httpclient5</artifactId> | |
| <version>5.4.3</version> | |
| </dependency> | |
| <dependency> | |
| <groupId>org.apache.httpcomponents.core5</groupId> | |
| <artifactId>httpcore5-h2</artifactId> | |
| <version>5.4.1</version> | |
| </dependency> | |
| </source> | |
| <p>These are the same dependencies used by Axis2's own | |
| <code>H2TransportSender</code>. If you are already running Axis2 | |
| with HTTP/2 transport, they are already on your classpath.</p> | |
| </section> | |
| <section name="Buffered Execution"> | |
| <p>POST JSON-RPC to any Axis2 service, get the response as a String:</p> | |
| <source> | |
| String url = "https://localhost:8443/axis2-json-api/services/FinancialBenchmarkService"; | |
| String json = "{\"monteCarlo\":[{\"arg0\":{\"nSimulations\":100000,\"nPeriods\":252," | |
| + "\"initialValue\":1000000,\"expectedReturn\":0.10,\"volatility\":0.223," | |
| + "\"nPeriodsPerYear\":252,\"randomSeed\":42}}]}"; | |
| String response = Http2JsonClient.execute(url, json, 300); | |
| System.out.println(response); | |
| Http2JsonClient.shutdown(); | |
| </source> | |
| <p>The client negotiates HTTP/2 via ALPN on the TLS handshake. | |
| Connections are pooled and multiplexed — multiple concurrent requests | |
| share a single TCP connection.</p> | |
| </section> | |
| <section name="Streaming Execution"> | |
| <p>For large responses (10MB+), stream to a file or parser instead | |
| of buffering in heap:</p> | |
| <source> | |
| String url = "https://localhost:8443/axis2-json-api/services/BigDataH2Service"; | |
| String json = "{\"generate\":[{\"arg0\":{\"datasetSize\":52428800}}]}"; | |
| try (FileOutputStream fos = new FileOutputStream("/tmp/result.json")) { | |
| int status = Http2JsonClient.executeStreaming(url, json, 300, fos); | |
| System.out.println("HTTP " + status); | |
| } | |
| Http2JsonClient.shutdown(); | |
| </source> | |
| <p>Each HTTP/2 DATA frame triggers a callback that writes directly | |
| to your <code>OutputStream</code>. The <code>capacityIncrement()</code> | |
| returns 64KB, creating natural HTTP/2 flow control backpressure — | |
| the client tells the server "I can accept 64KB more" after each | |
| chunk.</p> | |
| <p>When the server uses the | |
| <a href="json-streaming-formatter.html">Streaming JSON Formatter</a>, | |
| data flows end-to-end without full-body buffering on either side:</p> | |
| <source> | |
| Server (MoshiStreamingMessageFormatter) | |
| → FlushingOutputStream flushes every 64KB | |
| → HTTP/2 DATA frames | |
| → Http2JsonClient.data() callback | |
| → your OutputStream | |
| </source> | |
| </section> | |
| <section name="Timeout and Cancellation"> | |
| <p>Both methods accept a <code>timeoutSeconds</code> parameter for | |
| <code>Future.get()</code>. If the timeout expires or the thread is | |
| interrupted, the underlying HTTP request is cancelled to prevent | |
| zombie requests that would continue consuming resources:</p> | |
| <source> | |
| try { | |
| response = future.get(timeoutSeconds, TimeUnit.SECONDS); | |
| } catch (Exception e) { | |
| requestFuture.cancel(true); // Cancel the HTTP request | |
| if (e instanceof InterruptedException) { | |
| Thread.currentThread().interrupt(); // Restore interrupt flag | |
| } | |
| throw e; | |
| } | |
| </source> | |
| </section> | |
| <section name="Source Code"> | |
| <p>The complete sample client is available on GitHub:</p> | |
| <p><a href="https://github.com/apache/axis-axis2-java-core/blob/master/modules/samples/userguide/src/userguide/springbootdemo-tomcat11/src/main/java/userguide/springboot/client/Http2JsonClient.java"> | |
| Http2JsonClient.java on GitHub</a></p> | |
| <p>Copy and adapt it for your project. It has no dependency on | |
| Axis2 itself — only Apache HttpClient 5 and httpcore5-h2.</p> | |
| </section> | |
| </body> | |
| </document> |