/*
* Copyright 2011 Future Systems
*
* 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.krakenapps.httpd.impl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.timeout.IdleState;
import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;
import org.krakenapps.httpd.HttpConfiguration;
import org.krakenapps.httpd.HttpContext;
import org.krakenapps.httpd.HttpContextRegistry;
import org.krakenapps.httpd.VirtualHost;
import org.krakenapps.httpd.WebSocketFrame;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HttpServerHandler extends IdleStateAwareChannelHandler {
private final Logger logger = LoggerFactory.getLogger(HttpServerHandler.class.getName());
private BundleContext bc;
private HttpConfiguration config;
private HttpContextRegistry contextRegistry;
public HttpServerHandler(BundleContext bc, HttpConfiguration config, HttpContextRegistry contextRegistry) {
this.bc = bc;
this.config = config;
this.contextRegistry = contextRegistry;
}
@Override
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent event) {
if (event.getState() == IdleState.ALL_IDLE) {
Channel channel = ctx.getChannel();
if (logger.isDebugEnabled()) {
long idle = new Date().getTime() - event.getLastActivityTimeMillis();
logger.debug("kraken httpd: closing idle connection [local={}, remote={}, idle={}, state={}]",
new Object[] { channel.getLocalAddress(), channel.getRemoteAddress(), idle, event.getState() });
}
channel.close();
}
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage();
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
HttpContext httpContext = findHttpContext(req.getHeader(HttpHeaders.Names.HOST));
if (httpContext == null) {
Request request = new Request(ctx, req);
Response response = new Response(bc, ctx, request);
request.setResponse(response);
response.sendError(404);
return;
}
Request request = new Request(ctx, req);
Response response = new Response(bc, ctx, request);
request.setResponse(response);
httpContext.handle(request, response);
} else if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
HttpContext httpContext = findHttpContext(frame.getHost());
httpContext.getWebSocketManager().dispatch(frame);
}
}
private HttpContext findHttpContext(String host) {
if (host != null) {
for (VirtualHost v : config.getVirtualHosts())
if (v.matches(host))
return contextRegistry.findContext(v.getHttpContextName());
}
String contextName = config.getDefaultHttpContext();
if (contextName == null)
return null;
return contextRegistry.findContext(contextName);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
// At this moment, channel is already closed
// Do NOT call ctx.getChannel().close() again
for (String name : contextRegistry.getContextNames()) {
HttpContext context = contextRegistry.findContext(name);
InetSocketAddress remote = (InetSocketAddress) ctx.getChannel().getRemoteAddress();
context.getWebSocketManager().unregister(remote);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
List<String> trace = Arrays.asList("Connection reset by peer",
"An existing connection was forcibly closed by the remote host");
if (e.getCause() instanceof IOException && trace.contains(e.getCause().getMessage())) {
logger.trace("kraken httpd: connection reset", e.getCause());
} else if (e.getCause() instanceof ClosedChannelException) {
logger.trace("kraken httpd: connection closed", e.getCause());
} else {
logger.error("kraken httpd: transport error", e.getCause());
e.getChannel().close();
}
}
}