/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. All rights reserved.
*
* Licensed 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.
*/
package com.noctarius.tengi.server.impl.transport.http2;
import com.noctarius.tengi.core.model.Identifier;
import com.noctarius.tengi.core.model.Message;
import com.noctarius.tengi.core.model.Packet;
import com.noctarius.tengi.server.ServerTransports;
import com.noctarius.tengi.server.impl.transport.AbstractStreamingTransportTestCase;
import com.noctarius.tengi.spi.buffer.MemoryBuffer;
import com.noctarius.tengi.spi.buffer.impl.MemoryBufferFactory;
import com.noctarius.tengi.spi.connection.packets.Handshake;
import com.noctarius.tengi.spi.serialization.Serializer;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableEncoder;
import com.noctarius.tengi.spi.serialization.codec.impl.DefaultCodec;
import com.noctarius.tengi.spi.serialization.impl.DefaultProtocol;
import com.noctarius.tengi.spi.serialization.impl.DefaultProtocolConstants;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class Http2TransportTestCase
extends AbstractStreamingTransportTestCase {
@Test(timeout = 120000)
public void test_http2_transport()
throws Exception {
Serializer serializer = Serializer.create(new DefaultProtocol(Collections.emptyList()));
CompletableFuture<Object> future = new CompletableFuture<>();
Packet packet = new Packet("login");
packet.setValue("username", "Stan");
Message message = Message.create(packet);
ChannelReader<Http2TestClient, ByteBuf> channelReader = (client, buffer) -> {
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
DefaultCodec codec = new DefaultCodec(serializer.getProtocol(), memoryBuffer);
boolean loggedIn = codec.readBoolean();
Identifier connectionId = codec.readObject();
Object object = codec.readObject();
if (loggedIn && object instanceof Handshake) {
writeChannel(serializer, client, connectionId, message);
return;
}
future.complete(object);
};
Runner<Object, Http2TestClient> runner = (client) -> {
ByteBuf buffer = Unpooled.buffer();
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
DefaultCodec codec = new DefaultCodec(serializer.getProtocol(), memoryBuffer);
codec.writeBoolean("loggedIn", false);
codec.writeObject("handshake", new Handshake());
client.sendMessage(buffer);
Object result = future.get(120, TimeUnit.SECONDS);
client.close();
return result;
};
Object response = practice(runner, clientFactory(channelReader), false, ServerTransports.HTTP2_TRANSPORT);
assertEquals(message, response);
}
@Test(timeout = 120000)
public void test_http2_transport_ping_pong()
throws Exception {
Serializer serializer = Serializer.create(new DefaultProtocol(Collections.emptyList()));
CompletableFuture<Packet> future = new CompletableFuture<>();
ChannelReader<Http2TestClient, ByteBuf> channelReader = (client, buffer) -> {
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
DefaultCodec codec = new DefaultCodec(serializer.getProtocol(), memoryBuffer);
boolean loggedIn = codec.readBoolean();
Identifier connectionId = codec.readObject();
Object object = codec.readObject();
if (object instanceof Handshake) {
Packet packet = new Packet("pingpong");
packet.setValue("counter", 1);
Message message = Message.create(packet);
writeChannel(serializer, client, connectionId, message);
return;
}
Message message = (Message) object;
Packet packet = message.getBody();
int counter = packet.getValue("counter");
if (counter == 4) {
future.complete(packet);
} else {
packet.setValue("counter", counter + 1);
message = Message.create(packet);
ByteBuf buffer2 = Unpooled.buffer();
MemoryBuffer memoryBuffer2 = MemoryBufferFactory.create(buffer2);
DefaultCodec codec2 = new DefaultCodec(serializer.getProtocol(), memoryBuffer2);
codec2.writeBoolean("loggedIn", loggedIn);
codec2.writeObject("connectionId", connectionId);
serializer.writeObject("message", message, codec2);
client.sendMessage(buffer2);
}
};
Runner<Packet, Http2TestClient> runner = (client) -> {
ByteBuf buffer = Unpooled.buffer();
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
DefaultCodec codec = new DefaultCodec(serializer.getProtocol(), memoryBuffer);
codec.writeBoolean("loggedIn", false);
codec.writeObject("handshake", new Handshake());
client.sendMessage(buffer);
Packet result = future.get(120, TimeUnit.SECONDS);
client.close();
return result;
};
Packet response = practice(runner, clientFactory(channelReader), false, ServerTransports.HTTP2_TRANSPORT);
assertEquals(4, (int) response.getValue("counter"));
}
private static void writeChannel(Serializer serializer, Http2TestClient client, Identifier connectionId, Object value)
throws Exception {
ByteBuf buffer = Unpooled.directBuffer();
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
encoder.writeBoolean("loggedIn", true);
encoder.writeObject("connectionId", connectionId);
serializer.writeObject("value", value, encoder);
}
client.sendMessage(buffer);
}
private static ClientFactory<Http2TestClient> clientFactory(ChannelReader<Http2TestClient, ByteBuf> channelReader) {
return (host, port, ssl, group) -> new Http2TestClient(host, port, ssl, channelReader);
}
public static class Http2TestClient {
private final HTTP2Client client = new HTTP2Client();
private final Session session;
private final ChannelReader<Http2TestClient, ByteBuf> channelReader;
private final HttpURI httpURI;
private Http2TestClient(String host, int port, boolean ssl, ChannelReader<Http2TestClient, ByteBuf> channelReader)
throws Exception {
this.channelReader = channelReader;
this.httpURI = new HttpURI(createURI(host, port, ssl));
FuturePromise<Session> promise = new FuturePromise<>();
client.start();
client.connect(new InetSocketAddress(host, port), new Session.Listener.Adapter(), promise);
this.session = promise.get();
}
private URI createURI(String host, int port, boolean ssl) {
String url = (ssl ? "https" : "http") + "://" + host + ":" + port + "/channel";
return URI.create(url);
}
private void sendMessage(ByteBuf buffer)
throws Exception {
ByteBuffer nioBuffer = buffer.nioBuffer();
if (buffer.isDirect()) {
nioBuffer = Unpooled.copiedBuffer(buffer).nioBuffer();
}
HttpFields requestFields = new HttpFields();
requestFields.put(HttpHeader.CONTENT_TYPE, DefaultProtocolConstants.PROTOCOL_MIME_TYPE);
MetaData.Request request = new MetaData.Request("PUT", httpURI, HttpVersion.HTTP_2, requestFields);
HeadersFrame headersFrame = new HeadersFrame(0, request, null, false);
Stream.Listener listener = new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, org.eclipse.jetty.util.Callback callback) {
ByteBuf buf = Unpooled.wrappedBuffer(frame.getData());
try {
channelReader.channelRead(Http2TestClient.this, buf);
callback.succeeded();
} catch (Exception e) {
callback.failed(e);
}
}
};
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(headersFrame, promise, listener);
Stream stream = promise.get();
DataFrame requestContent = new DataFrame(stream.getId(), nioBuffer, true);
stream.data(requestContent, org.eclipse.jetty.util.Callback.Adapter.INSTANCE);
}
private void close()
throws Exception {
client.stop();
}
}
}