blob: 6f52e71f2107f32950c3bfa6e422c28f88d96d08 [file]
<?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>
&lt;!-- Maven --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.apache.httpcomponents.client5&lt;/groupId&gt;
&lt;artifactId&gt;httpclient5&lt;/artifactId&gt;
&lt;version&gt;5.4.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.apache.httpcomponents.core5&lt;/groupId&gt;
&lt;artifactId&gt;httpcore5-h2&lt;/artifactId&gt;
&lt;version&gt;5.4.1&lt;/version&gt;
&lt;/dependency&gt;
</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>