/*
* 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.http;
import com.noctarius.tengi.core.connection.Connection;
import com.noctarius.tengi.core.connection.HandshakeHandler;
import com.noctarius.tengi.core.connection.Transport;
import com.noctarius.tengi.core.model.Identifier;
import com.noctarius.tengi.core.model.Message;
import com.noctarius.tengi.server.ServerTransports;
import com.noctarius.tengi.server.impl.ConnectionManager;
import com.noctarius.tengi.spi.buffer.MemoryBuffer;
import com.noctarius.tengi.spi.buffer.impl.MemoryBufferFactory;
import com.noctarius.tengi.spi.connection.ConnectionContext;
import com.noctarius.tengi.spi.connection.packets.Handshake;
import com.noctarius.tengi.spi.serialization.Serializer;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableDecoder;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableEncoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http2.DefaultHttp2Connection;
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
import io.netty.handler.codec.http2.Http2ConnectionHandler;
import io.netty.handler.codec.http2.Http2Error;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2FrameAdapter;
import io.netty.handler.codec.http2.Http2Headers;
import static com.noctarius.tengi.server.impl.ServerUtil.CONNECTION_ID;
import static com.noctarius.tengi.server.impl.ServerUtil.connectionAttribute;
class Http2ConnectionProcessor
extends Http2ConnectionHandler {
private final ConnectionManager connectionManager;
Http2ConnectionProcessor(ConnectionManager connectionManager, Serializer serializer) {
super(new DefaultHttp2Connection(true), new InternalFrameAdapter());
InternalFrameAdapter adapter = ((InternalFrameAdapter) decoder().listener());
adapter.encoder(encoder());
adapter.serializer(serializer);
adapter.connectionManager(connectionManager);
adapter.transport(ServerTransports.HTTP2_TRANSPORT);
this.connectionManager = connectionManager;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.channel().close();
Identifier connectionId = connectionAttribute(ctx, CONNECTION_ID);
connectionManager.exceptionally(connectionId, cause);
super.exceptionCaught(ctx, cause);
}
private static class InternalFrameAdapter
extends Http2FrameAdapter {
private Http2ConnectionEncoder encoder;
private ConnectionManager connectionManager;
private Serializer serializer;
private Transport transport;
private void encoder(Http2ConnectionEncoder encoder) {
this.encoder = encoder;
}
private void connectionManager(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
private void serializer(Serializer serializer) {
this.serializer = serializer;
}
private void transport(Transport transport) {
this.transport = transport;
}
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endStream)
throws Http2Exception {
super.onHeadersRead(ctx, streamId, headers, padding, endStream);
}
@Override
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
throws Http2Exception {
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(data);
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
if (decoder == null) {
ctx.close();
return 0;
}
boolean loggedIn = decoder.readBoolean();
if (!loggedIn) {
handleHandshakeRequest(ctx, decoder, streamId, encoder);
return 0;
}
Identifier connectionId = decoder.readObject();
connectionAttribute(ctx, CONNECTION_ID, connectionId);
Message message = decoder.readObject();
connectionManager.publishMessage(ctx.channel(), connectionId, message);
} catch (Exception e) {
throw new Http2Exception(Http2Error.INTERNAL_ERROR, "Internal Server Error", e);
}
return data.writerIndex();
}
private void handleHandshakeRequest(ChannelHandlerContext ctx, AutoClosableDecoder decoder, int streamId,
Http2ConnectionEncoder encoder)
throws Exception {
Object request = decoder.readObject();
if (!(request instanceof Handshake)) {
ctx.close();
return;
}
Identifier connectionId = Identifier.randomIdentifier();
HandshakeHandler handshakeHandler = connectionManager.getHandshakeHandler();
Handshake handshakeResponse = handshakeHandler.handleHandshake(connectionId, (Handshake) request);
if (handshakeResponse == null) {
ctx.close();
return;
}
if (handshakeResponse == request) {
ctx.close();
throw new IllegalStateException("Handshake could not be accepted, illegal verification");
}
connectionAttribute(ctx, CONNECTION_ID, connectionId);
ConnectionContext connectionContext = createConnectionContext(ctx, connectionId, streamId, encoder);
Connection connection = connectionManager.assignConnection(connectionId, connectionContext, transport);
connectionContext.writeSocket(encoder, connection, createHandshakeResponse(ctx, handshakeResponse));
}
private MemoryBuffer createHandshakeResponse(ChannelHandlerContext ctx, Handshake handshakeResponse)
throws Exception {
ByteBuf buffer = ctx.alloc().buffer();
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
encoder.writeObject("response", handshakeResponse);
}
return memoryBuffer;
}
private ConnectionContext<Http2ConnectionEncoder> createConnectionContext(ChannelHandlerContext ctx,
Identifier connectionId, int streamId,
Http2ConnectionEncoder encoder) {
return new Http2ConnectionContext(connectionId, serializer, transport, encoder, streamId, ctx);
}
}
}