/*
* ====================================================================
* 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.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.function.Supplier;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Message;
import org.apache.hc.core5.http.impl.nio.bootstrap.ClientEndpoint;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
import org.apache.hc.core5.http.nio.BasicRequestProducer;
import org.apache.hc.core5.http.nio.BasicResponseConsumer;
import org.apache.hc.core5.http.nio.entity.NoopEntityConsumer;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
import org.apache.hc.core5.http.nio.support.BasicAsyncPushHandler;
import org.apache.hc.core5.http2.config.H2Config;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.testing.nio.http2.Http2TestClient;
public class Http2CompatibilityTest {
enum ServerType { DEFAULT, APACHE_HTTPD, NGINX }
private final HttpHost target;
private final H2Config h2Config;
private final Http2TestClient client;
public static void main(final String... args) throws Exception {
final HttpHost target = args.length > 0 ? HttpHost.create(args[0]) : new HttpHost("localhost", 8080);
final ServerType serverType = args.length > 1 ? ServerType.valueOf(args[1].toUpperCase(Locale.ROOT)) : ServerType.DEFAULT;
final H2Config h2Config;
switch (serverType) {
case APACHE_HTTPD:
h2Config = H2Config.custom()
.setPushEnabled(true)
// Required for compatibility with Apache HTTPD 2.4
.setSettingAckNeeded(false)
.build();
break;
case NGINX:
h2Config = H2Config.custom()
.setPushEnabled(true)
.build();
break;
default:
h2Config = H2Config.DEFAULT;
}
final Http2CompatibilityTest test = new Http2CompatibilityTest(target, h2Config);
try {
test.start();
test.execute();
} finally {
test.shutdown();
}
}
Http2CompatibilityTest(final HttpHost target, final H2Config h2Config) throws Exception {
this.target = target;
this.h2Config = h2Config;
this.client = new Http2TestClient(IOReactorConfig.DEFAULT, null);
}
void start() throws Exception {
client.start(h2Config);
}
void shutdown() throws Exception {
client.shutdown(5, TimeUnit.SECONDS);
}
private final static String[] REQUEST_URIS = new String[] {"/", "/news.html", "/status.html"};
void execute() throws Exception {
final Future connectFuture = client.connect(target, 5, TimeUnit.SECONDS);
final ClientEndpoint clientEndpoint = connectFuture.get(5, TimeUnit.SECONDS);
final BlockingDeque resultQueue = new LinkedBlockingDeque<>();
if (h2Config.isPushEnabled()) {
client.register("*", new Supplier() {
@Override
public AsyncPushConsumer get() {
return new BasicAsyncPushHandler(new BasicResponseConsumer<>(new NoopEntityConsumer())) {
@Override
protected void handleResponse(
final HttpRequest promise,
final Message responseMessage) throws IOException, HttpException {
resultQueue.add(new RequestResult(promise, responseMessage.getHead(), null));
}
@Override
protected void handleError(
final HttpRequest promise,
final Exception cause) {
resultQueue.add(new RequestResult(promise, null, cause));
}
};
}
});
}
for (final String requestUri: REQUEST_URIS) {
final HttpRequest request = new BasicHttpRequest("GET", target, requestUri);
clientEndpoint.execute(
new BasicRequestProducer(request, null),
new BasicResponseConsumer<>(new StringAsyncEntityConsumer()),
new FutureCallback>() {
@Override
public void completed(final Message responseMessage) {
resultQueue.add(new RequestResult(request, responseMessage.getHead(), null));
}
@Override
public void failed(final Exception ex) {
resultQueue.add(new RequestResult(request, null, ex));
}
@Override
public void cancelled() {
resultQueue.add(new RequestResult(request, null, new CancellationException()));
}
});
}
String serverInfo = null;
final Map resultMap = new HashMap<>();
for (;;) {
final RequestResult entry = resultQueue.poll(3, TimeUnit.SECONDS);
if (entry != null) {
final HttpRequest request = entry.request;
final HttpResponse response = entry.response;
if (response != null) {
final Header header = response.getFirstHeader(HttpHeaders.SERVER);
if (header != null) {
serverInfo = header.getValue();
}
}
resultMap.put(request.getRequestUri(), entry);
} else {
break;
}
}
clientEndpoint.close();
System.out.println("Server info: " + serverInfo);
for (final String requestUri: REQUEST_URIS) {
final RequestResult entry = resultMap.remove(requestUri);
if (entry != null) {
final HttpResponse response = entry.response;
final Exception exception = entry.exception;
if (exception != null) {
System.out.println("NOK: " + requestUri + " -> " + exception.getMessage());
} if (response != null) {
System.out.println((response.getCode() == HttpStatus.SC_OK ? "OK: " : "NOK: ") +
requestUri + " -> " + response.getCode());
}
}
}
if (h2Config.isPushEnabled() && resultMap.isEmpty()) {
System.out.println("NOK: pushed responses expected");
}
for (final Iterator it = resultMap.values().iterator(); it.hasNext(); ) {
final RequestResult entry = it.next();
final HttpRequest request = entry.request;
final HttpResponse response = entry.response;
final Exception exception = entry.exception;
if (exception != null) {
System.out.println("NOK: " + request.getRequestUri() + " (pushed) -> " + exception.getMessage());
} if (response != null) {
System.out.println((response.getCode() == HttpStatus.SC_OK ? "OK: " : "NOK: ") +
request.getRequestUri() + " (pushed) -> " + response.getCode());
}
}
}
static class RequestResult {
final HttpRequest request;
final HttpResponse response;
final Exception exception;
RequestResult(final HttpRequest request, final HttpResponse response, final Exception exception) {
this.request = request;
this.response = response;
this.exception = exception;
}
}
}