/*
* Copyright (C) 2013 Google Inc.
*
* 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.ros.internal.xmlrpc.webserver;
import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static org.jboss.netty.handler.codec.http.HttpHeaders.setContentLength;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpMethod.POST;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
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.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.util.CharsetUtil;
import java.io.IOException;
/**
* Web server handler for the Netty XMLRPC handler
*
* @author Keith M. Hughes
*/
public class NettyXmlRpcWebServerHandler extends SimpleChannelUpstreamHandler {
/**
* The web server this handler is attached to
*/
private NettyXmlRpcWebServer webServer;
public NettyXmlRpcWebServerHandler(NettyXmlRpcWebServer webServer) {
this.webServer = webServer;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage();
if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg);
} else {
webServer.getLog().warn(
String.format("Web server received unknown frame %s", msg.getClass().getName()));
}
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
webServer.channelOpened(e.getChannel());
}
/**
* Handle an HTTP request coming into the server.
*
* @param ctx
* The channel context for the request.
* @param req
* The HTTP request.
*
* @throws Exception
*/
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
if (handleWebRequest(ctx, req)) {
// The method handled the request if the return value was true.
} else {
// Nothing we handle.
webServer.getLog().warn(
String.format("Web server has no handlers for request %s", req.getUri()));
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
}
}
/**
* Attempt to handle an HTTP request by scanning through all registered
* handlers.
*
* @param ctx
* The context for the request.
* @param req
* The request.
* @return True if the request was handled, false otherwise.
*/
private boolean handleWebRequest(ChannelHandlerContext ctx, HttpRequest req) throws IOException {
if (req.getMethod() != POST) {
return false;
}
XmlRpcServerClientConnection connection =
new XmlRpcServerClientConnection(ctx, req, webServer.getXmlRpcServer(), this);
connection.process();
return true;
}
/**
* Send an HTTP response to the client.
*
* @param ctx
* the channel event context
* @param req
* the request which has come in
* @param res
* the response which is being written
*/
public void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != HttpResponseStatus.OK.getCode()) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes());
}
Channel channel = ctx.getChannel();
if (!channel.isOpen()) {
webServer.getLog().warn("Attempting to send XML RPC response but channel is closed");
return;
}
// Send the response and close the connection if necessary.
ChannelFuture f = channel.write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != HttpResponseStatus.OK.getCode()
|| req.getMethod() == POST) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
/**
* Send a success response to the client.
*
* @param ctx
* the channel event context
* @param req
* the request which has come in
*/
private void sendSuccessHttpResponse(ChannelHandlerContext ctx, HttpRequest req) {
DefaultHttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
sendHttpResponse(ctx, req, res);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
webServer.getLog().error("Exception caught in the web server", e.getCause());
e.getChannel().close();
}
/**
* Send an error to the remote machine.
*
* @param ctx
* handler context
* @param status
* the status to send
*/
public void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.setContent(ChannelBuffers.copiedBuffer("Failure: " + status.toString() + "\r\n",
CharsetUtil.UTF_8));
// Close the connection as soon as the error message is sent.
ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
}
/**
* Get the webserver for the handler.
*
* @return the webserver for the handler
*/
public NettyXmlRpcWebServer getWebServer() {
return webServer;
}
}