/*
* ====================================================================
* 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.compatibility.http2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hc.core5.http.ContentType;
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.nio.bootstrap.ClientEndpoint;
import org.apache.hc.core5.http.impl.nio.entity.AbstractClassicEntityConsumer;
import org.apache.hc.core5.http.impl.nio.entity.AbstractClassicEntityProducer;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.nio.BasicRequestProducer;
import org.apache.hc.core5.http.nio.BasicResponseConsumer;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
import org.apache.hc.core5.http2.config.H2Config;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.testing.nio.http2.Http2TestClient;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class JettyHttp2CompatibilityTest extends JettyServerTestBase {
private static final long TIMEOUT = 5;
private Http2TestClient client;
@Before
public void setup() throws Exception {
client = new Http2TestClient(IOReactorConfig.DEFAULT, null);
}
@After
public void cleanup() throws Exception {
if (client != null) {
client.shutdown(3, TimeUnit.SECONDS);
}
}
private URI createRequestURI(final URI serverEndpoint, final String path) throws URISyntaxException {
return new URI(serverEndpoint.getScheme(), serverEndpoint.getAuthority(), path, null, null);
}
static class SingleLineResponseHandler extends AbstractHandler {
private final String message;
SingleLineResponseHandler(final String message) {
this.message = message;
}
@Override
public void handle(
final String target,
final Request baseRequest,
final HttpServletRequest request,
final HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
final PrintWriter out = response.getWriter();
out.print(message);
out.flush();
baseRequest.setHandled(true);
}
}
@Test
public void testSimpleGet() throws Exception {
final ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(new SingleLineResponseHandler("Hi there"));
server.setHandler(contextHandler);
server.start();
final URI serverEndpoint = server.getURI();
client.start();
final Future connectFuture = client.connect(
"localhost", serverEndpoint.getPort(), TIMEOUT, TimeUnit.SECONDS);
final ClientEndpoint streamEndpoint = connectFuture.get();
final Queue>> queue = new LinkedList<>();
for (int i = 0; i < 10; i++) {
queue.add(streamEndpoint.execute(
new BasicRequestProducer("GET", createRequestURI(serverEndpoint, "/hello")),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null));
}
while (!queue.isEmpty()) {
final Future> future = queue.remove();
final Message result = future.get(TIMEOUT, TimeUnit.SECONDS);
Assert.assertNotNull(result);
final HttpResponse response = result.getHead();
final String entity = result.getBody();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getCode());
Assert.assertEquals("Hi there", entity);
}
}
static class MultiLineResponseHandler extends AbstractHandler {
private final String message;
private final int count;
MultiLineResponseHandler(final String message, final int count) {
this.message = message;
this.count = count;
}
@Override
public void handle(
final String target,
final Request baseRequest,
final HttpServletRequest request,
final HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
final PrintWriter out = response.getWriter();
for (int i = 0; i < count; i++) {
out.println(message);
}
out.flush();
baseRequest.setHandled(true);
}
}
@Test
public void testLargeGet() throws Exception {
final ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(new MultiLineResponseHandler("0123456789abcdef", 5000));
server.setHandler(contextHandler);
server.start();
final URI serverEndpoint = server.getURI();
client.start();
final Future connectFuture = client.connect(
"localhost", serverEndpoint.getPort(), TIMEOUT, TimeUnit.SECONDS);
final ClientEndpoint streamEndpoint = connectFuture.get();
final HttpRequest request1 = new BasicHttpRequest("GET", createRequestURI(serverEndpoint, "/"));
final Future> future1 = streamEndpoint.execute(
new BasicRequestProducer(request1, null),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
final HttpRequest request2 = new BasicHttpRequest("GET", createRequestURI(serverEndpoint, "/"));
final Future> future2 = streamEndpoint.execute(
new BasicRequestProducer(request2, null),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer(512)), null);
final Message result1 = future1.get();
Assert.assertNotNull(result1);
final HttpResponse response1 = result1.getHead();
Assert.assertNotNull(response1);
Assert.assertEquals(200, response1.getCode());
final String s1 = result1.getBody();
Assert.assertNotNull(s1);
final StringTokenizer t1 = new StringTokenizer(s1, "\r\n");
while (t1.hasMoreTokens()) {
Assert.assertEquals("0123456789abcdef", t1.nextToken());
}
final Message result2 = future2.get();
Assert.assertNotNull(result2);
final HttpResponse response2 = result2.getHead();
Assert.assertNotNull(response2);
Assert.assertEquals(200, response2.getCode());
final String s2 = result2.getBody();
Assert.assertNotNull(s2);
final StringTokenizer t2 = new StringTokenizer(s2, "\r\n");
while (t2.hasMoreTokens()) {
Assert.assertEquals("0123456789abcdef", t2.nextToken());
}
}
@Test
public void testBasicPost() throws Exception {
final ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(new SingleLineResponseHandler("Hi back"));
server.setHandler(contextHandler);
server.start();
final URI serverEndpoint = server.getURI();
client.start();
final Future connectFuture = client.connect(
"localhost", serverEndpoint.getPort(), TIMEOUT, TimeUnit.SECONDS);
final ClientEndpoint streamEndpoint = connectFuture.get();
final Queue>> queue = new LinkedList<>();
for (int i = 0; i < 10; i++) {
final HttpRequest request = new BasicHttpRequest("POST", createRequestURI(serverEndpoint, "/hello"));
queue.add(streamEndpoint.execute(
new BasicRequestProducer(request, new StringAsyncEntityProducer("Hi there", ContentType.TEXT_PLAIN)),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null));
}
while (!queue.isEmpty()) {
final Future> future = queue.remove();
final Message result = future.get(TIMEOUT, TimeUnit.SECONDS);
Assert.assertNotNull(result);
final HttpResponse response = result.getHead();
final String entity1 = result.getBody();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getCode());
Assert.assertEquals("Hi back", entity1);
}
}
static class EchoHandler extends AbstractHandler {
@Override
public void handle(
final String target,
final Request baseRequest,
final HttpServletRequest request,
final HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
final BufferedReader reader = request.getReader();
final PrintWriter out = response.getWriter();
String line;
while ((line = reader.readLine()) != null) {
out.println(line);
}
out.flush();
baseRequest.setHandled(true);
}
}
@Test
public void testSlowResponseConsumer() throws Exception {
final ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(new MultiLineResponseHandler("0123456789abcdef", 3));
server.setHandler(contextHandler);
server.start();
final URI serverEndpoint = server.getURI();
client = new Http2TestClient();
client.start(H2Config.custom().setInitialWindowSize(16).build());
final Future connectFuture = client.connect(
"localhost", serverEndpoint.getPort(), TIMEOUT, TimeUnit.SECONDS);
final ClientEndpoint streamEndpoint = connectFuture.get();
final Future> future1 = streamEndpoint.execute(
new BasicRequestProducer("GET", createRequestURI(serverEndpoint, "/"), null),
new BasicResponseConsumer<>(new AbstractClassicEntityConsumer(16, Executors.newSingleThreadExecutor()) {
@Override
protected String consumeData(
final ContentType contentType, final InputStream inputStream) throws IOException {
Charset charset = contentType != null ? contentType.getCharset() : null;
if (charset == null) {
charset = StandardCharsets.US_ASCII;
}
final StringBuilder buffer = new StringBuilder();
try {
final byte[] tmp = new byte[16];
int l;
while ((l = inputStream.read(tmp)) != -1) {
buffer.append(charset.decode(ByteBuffer.wrap(tmp, 0, l)));
Thread.sleep(500);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new InterruptedIOException(ex.getMessage());
}
return buffer.toString();
}
}),
null);
final Message result1 = future1.get(TIMEOUT, TimeUnit.SECONDS);
Assert.assertNotNull(result1);
final HttpResponse response1 = result1.getHead();
Assert.assertNotNull(response1);
Assert.assertEquals(200, response1.getCode());
final String s1 = result1.getBody();
Assert.assertNotNull(s1);
final StringTokenizer t1 = new StringTokenizer(s1, "\r\n");
while (t1.hasMoreTokens()) {
Assert.assertEquals("0123456789abcdef", t1.nextToken());
}
}
@Test
public void testSlowRequestProducer() throws Exception {
final ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(new EchoHandler());
server.setHandler(contextHandler);
server.start();
final URI serverEndpoint = server.getURI();
client.start();
final Future connectFuture = client.connect(
"localhost", serverEndpoint.getPort(), TIMEOUT, TimeUnit.SECONDS);
final ClientEndpoint streamEndpoint = connectFuture.get();
final HttpRequest request1 = new BasicHttpRequest("POST", createRequestURI(serverEndpoint, "/echo"));
final Future> future1 = streamEndpoint.execute(
new BasicRequestProducer(request1, new AbstractClassicEntityProducer(4096, ContentType.TEXT_PLAIN, Executors.newSingleThreadExecutor()) {
@Override
protected void produceData(final ContentType contentType, final OutputStream outputStream) throws IOException {
Charset charset = contentType.getCharset();
if (charset == null) {
charset = StandardCharsets.US_ASCII;
}
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) {
for (int i = 0; i < 500; i++) {
if (i % 100 == 0) {
writer.flush();
Thread.sleep(500);
}
writer.write("0123456789abcdef\r\n");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new InterruptedIOException(ex.getMessage());
}
}
}),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
final Message result1 = future1.get();
Assert.assertNotNull(result1);
final HttpResponse response1 = result1.getHead();
Assert.assertNotNull(response1);
Assert.assertEquals(200, response1.getCode());
final String s1 = result1.getBody();
Assert.assertNotNull(s1);
final StringTokenizer t1 = new StringTokenizer(s1, "\r\n");
while (t1.hasMoreTokens()) {
Assert.assertEquals("0123456789abcdef", t1.nextToken());
};
}
}