/**
* Copyright 2007-2015, Kaazing Corporation. 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 org.kaazing.k3po.driver.internal.netty.bootstrap.http;
import static java.lang.String.format;
import static org.jboss.netty.channel.Channels.fireChannelBound;
import static org.jboss.netty.channel.Channels.fireChannelClosed;
import static org.jboss.netty.channel.Channels.fireChannelUnbound;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ChildChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.ChannelGroupFutureListener;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import org.kaazing.k3po.driver.internal.netty.bootstrap.ServerBootstrap;
import org.kaazing.k3po.driver.internal.netty.bootstrap.channel.AbstractServerChannelSink;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddress;
public class HttpServerChannelSink extends AbstractServerChannelSink<HttpServerChannel> {
private final ConcurrentNavigableMap<ChannelAddress, HttpServerChannel> httpBindings;
private final ConcurrentMap<ChannelAddress, HttpTransport> httpTransports;
private final ChannelPipelineFactory pipelineFactory;
public HttpServerChannelSink() {
this(new ConcurrentSkipListMap<ChannelAddress, HttpServerChannel>(ChannelAddress.ADDRESS_COMPARATOR));
}
private HttpServerChannelSink(ConcurrentNavigableMap<ChannelAddress, HttpServerChannel> httpBindings) {
this.pipelineFactory = new HttpChildChannelPipelineFactory(httpBindings);
this.httpBindings = httpBindings;
this.httpTransports = new ConcurrentHashMap<>();
}
@Override
protected void bindRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final HttpServerChannel httpBindChannel = (HttpServerChannel) evt.getChannel();
final ChannelFuture httpBindFuture = evt.getFuture();
final ChannelAddress httpLocalAddress = (ChannelAddress) evt.getValue();
URI httpLocation = httpLocalAddress.getLocation();
HttpServerChannel httpBoundChannel = httpBindings.putIfAbsent(httpLocalAddress, httpBindChannel);
if (httpBoundChannel != null) {
httpBindFuture.setFailure(new ChannelException(format("Duplicate bind failed: %s", httpLocation)));
}
ChannelAddress address = httpLocalAddress.getTransport();
HttpTransport httpTransport = httpTransports.get(address);
if (httpTransport == null) {
String schemeName = address.getLocation().getScheme();
String httpSchemeName = httpLocalAddress.getLocation().getScheme();
ServerBootstrap bootstrap = bootstrapFactory.newServerBootstrap(schemeName);
bootstrap.setParentHandler(createParentHandler(httpBindChannel, address));
bootstrap.setPipelineFactory(pipelineFactory);
bootstrap.setOption(format("%s.nextProtocol", schemeName), httpSchemeName);
// bind transport
ChannelFuture bindFuture = bootstrap.bindAsync(address);
HttpTransport newHttpTransport = new HttpTransport(bindFuture, 1);
httpTransport = httpTransports.putIfAbsent(address, newHttpTransport);
if (httpTransport == null) {
httpTransport = newHttpTransport;
}
}
else {
httpTransport.count.incrementAndGet();
}
if (httpTransport.future.isDone()) {
handleHttpTransportBindComplete(httpBindChannel, httpBindFuture, httpLocalAddress, httpTransport.future);
}
else {
httpTransport.future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
handleHttpTransportBindComplete(httpBindChannel, httpBindFuture, httpLocalAddress, future);
}
});
}
}
@Override
protected void unbindRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final HttpServerChannel httpUnbindChannel = (HttpServerChannel) evt.getChannel();
final ChannelFuture httpUnbindFuture = evt.getFuture();
ChannelAddress httpLocalAddress = httpUnbindChannel.getLocalAddress();
if (!httpBindings.remove(httpLocalAddress, httpUnbindChannel)) {
httpUnbindFuture.setFailure(new ChannelException("Channel not bound").fillInStackTrace());
return;
}
ChannelAddress address = httpLocalAddress.getTransport();
HttpTransport httpTransport = httpTransports.get(address);
assert httpTransport != null;
if (httpTransport.count.decrementAndGet() == 0) {
// ensure only zero count is removed
HttpTransport oldHttpTransport = new HttpTransport(httpTransport.future);
if (httpTransports.remove(address, oldHttpTransport)) {
// unbind transport
Channel transport = httpUnbindChannel.getTransport();
ChannelFuture unbindFuture = transport.unbind();
if (unbindFuture.isDone()) {
handleHttpTransportUnbindComplete(httpUnbindChannel, httpUnbindFuture, unbindFuture);
}
else {
unbindFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture unbindFuture) throws Exception {
handleHttpTransportUnbindComplete(httpUnbindChannel, httpUnbindFuture, unbindFuture);
}
});
}
}
}
else {
fireChannelUnbound(httpUnbindChannel);
httpUnbindFuture.setSuccess();
}
}
@Override
protected void closeRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final HttpServerChannel httpCloseChannel = (HttpServerChannel) evt.getChannel();
final ChannelFuture httpCloseFuture = evt.getFuture();
boolean wasBound = httpCloseChannel.isBound();
if (httpCloseChannel.setClosed()) {
if (wasBound) {
unbindRequested(pipeline, evt);
}
Channel transport = httpCloseChannel.getTransport();
if (transport != null) {
ChannelFuture closeFuture = transport.close();
if (closeFuture.isDone()) {
handleHttpTransportCloseComplete(httpCloseChannel, httpCloseFuture, closeFuture);
}
else {
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture closeFuture) throws Exception {
handleHttpTransportCloseComplete(httpCloseChannel, httpCloseFuture, closeFuture);
}
});
}
}
}
}
private ChannelHandler createParentHandler(HttpServerChannel channel, final ChannelAddress address) {
return new SimpleChannelHandler() {
private final ChannelGroup childChannels = new DefaultChannelGroup();
@Override
public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
e.getChannel().setAttachment(address);
childChannels.add(e.getChildChannel());
super.childChannelOpen(ctx, e);
}
@Override
public void childChannelClosed(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
childChannels.remove(e.getChildChannel());
super.childChannelClosed(ctx, e);
}
@Override
public void closeRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
// close up any transports previously in use for HTTP
childChannels.close().addListener(new ChannelGroupFutureListener() {
@Override
public void operationComplete(ChannelGroupFuture future) throws Exception {
ctx.sendDownstream(e);
}
});
}
};
}
private static void handleHttpTransportBindComplete(
HttpServerChannel httpBindChannel,
ChannelFuture httpBindFuture,
ChannelAddress httpLocalAddress,
ChannelFuture bindFuture) {
if (bindFuture.isSuccess()) {
httpBindChannel.setTransport(bindFuture.getChannel());
httpBindChannel.setLocalAddress(httpLocalAddress);
httpBindChannel.setBound();
fireChannelBound(httpBindChannel, httpBindChannel.getLocalAddress());
httpBindFuture.setSuccess();
}
else {
httpBindFuture.setFailure(bindFuture.getCause());
}
}
private static void handleHttpTransportUnbindComplete(
HttpServerChannel httpUnbindChannel,
ChannelFuture httpUnbindFuture,
ChannelFuture unbindFuture) {
if (unbindFuture.isSuccess()) {
fireChannelUnbound(httpUnbindChannel);
httpUnbindFuture.setSuccess();
}
else {
httpUnbindFuture.setFailure(unbindFuture.getCause());
}
}
private static void handleHttpTransportCloseComplete(
HttpServerChannel httpCloseChannel,
ChannelFuture httpCloseFuture,
ChannelFuture closeFuture) {
if (closeFuture.isSuccess()) {
fireChannelClosed(httpCloseChannel);
httpCloseFuture.setSuccess();
}
else {
httpCloseFuture.setFailure(closeFuture.getCause());
}
}
private static final class HttpTransport {
final ChannelFuture future;
final AtomicInteger count;
HttpTransport(ChannelFuture future) {
this(future, 0);
}
HttpTransport(ChannelFuture future, int count) {
this.future = future;
this.count = new AtomicInteger(count);
}
@Override
public int hashCode() {
return Objects.hash(future, count);
}
@Override
public boolean equals(Object obj) {
HttpTransport that = (HttpTransport) obj;
return Objects.equals(this.future, that.future) &&
this.count.get() == that.count.get();
}
@Override
public String toString() {
return format("[future=@%d, count=%d]", Objects.hashCode(future), count.get());
}
}
}