de.cosmocode.palava.ipc.xml.rpc.HttpHandler.java Source code

Java tutorial

Introduction

Here is the source code for de.cosmocode.palava.ipc.xml.rpc.HttpHandler.java

Source

/**
 * Copyright 2010 CosmoCode GmbH
 *
 * 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 de.cosmocode.palava.ipc.xml.rpc;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.Set;

import javax.annotation.concurrent.ThreadSafe;

import org.apache.commons.lang.StringUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandler.Sharable;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.Cookie;
import org.jboss.netty.handler.codec.http.CookieDecoder;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import de.cosmocode.palava.ipc.IpcSession;
import de.cosmocode.palava.ipc.IpcSessionProvider;
import de.cosmocode.palava.ipc.netty.ConnectionManager;
import de.cosmocode.palava.ipc.protocol.DetachedConnection;

/**
 * A {@link ChannelHandler} which decodes {@link HttpRequest}s
 * into {@link ChannelBuffer}s and encodes {@link ChannelBuffer}s into
 * {@link HttpResponse}s.
 *
 * @since 1.0
 * @author Willi Schoenborn
 */
@Sharable
@ThreadSafe
final class HttpHandler extends SimpleChannelHandler {

    private static final Logger LOG = LoggerFactory.getLogger(HttpHandler.class);

    private final IpcSessionProvider provider;

    private final ConnectionManager manager;

    private String cookieName = "psessid";

    @Inject
    public HttpHandler(IpcSessionProvider provider, ConnectionManager manager) {
        this.provider = Preconditions.checkNotNull(provider, "Provider");
        this.manager = Preconditions.checkNotNull(manager, "Manager");
    }

    @Inject(optional = true)
    public void setCookieName(@Named(XmlRpc.COOKIE_NAME) String cookieName) {
        this.cookieName = Preconditions.checkNotNull(cookieName, "CookieName");
    }

    @Override
    public void messageReceived(ChannelHandlerContext context, MessageEvent event) throws Exception {
        final Object message = event.getMessage();
        if (message instanceof HttpRequest) {
            final HttpRequest request = HttpRequest.class.cast(message);
            final SocketAddress remoteAddress = event.getRemoteAddress();

            final Set<Cookie> cookies = getCookies(request);
            final String sessionId = findSessionId(cookies);

            final String identifier;
            if (remoteAddress instanceof InetSocketAddress) {
                identifier = InetSocketAddress.class.cast(remoteAddress).getHostName();
            } else {
                identifier = null;
            }

            final IpcSession session = provider.getSession(sessionId, identifier);
            final DetachedConnection connection = manager.get(event.getChannel());
            connection.attachTo(session);

            context.setAttachment(new Attachment(request, cookies));
            LOG.trace("Decoding {} into channel buffer", request);
            Channels.fireMessageReceived(context, request.getContent(), remoteAddress);
        } else {
            context.sendUpstream(event);
        }
    }

    private Set<Cookie> getCookies(HttpRequest request) {
        final CookieDecoder decoder = new CookieDecoder();
        final String cookieString = request.getHeader(Names.COOKIE);
        if (StringUtils.isBlank(cookieString))
            return Collections.emptySet();
        return decoder.decode(cookieString);
    }

    private String findSessionId(Set<Cookie> cookies) {
        for (Cookie cookie : cookies) {
            if (cookieName.equals(cookie.getName())) {
                LOG.trace("Found sessionId in cookie: {}", cookie.getValue());
                return cookie.getValue();
            }
        }
        return null;
    }

    @Override
    public void writeRequested(ChannelHandlerContext context, MessageEvent event) throws Exception {
        final Object message = event.getMessage();
        if (message instanceof ChannelBuffer) {
            final Attachment attachment = Attachment.class.cast(context.getAttachment());
            Preconditions.checkState(attachment != null, "No attachment set");
            final ChannelBuffer content = ChannelBuffer.class.cast(message);

            LOG.trace("Encoding {} into http response", content);
            final HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

            // setting all requires headers
            response.setHeader(Names.CONTENT_TYPE, "text/xml");
            response.setHeader(Names.CONTENT_LENGTH, content.readableBytes());

            final DetachedConnection connection = manager.get(event.getChannel());
            final IpcSession session = connection.getSession();

            final CookieEncoder cookieEncoder = new CookieEncoder(true);
            for (Cookie cookie : attachment.getCookies()) {
                cookieEncoder.addCookie(cookie);
            }

            final String sessionId = session.getSessionId();
            LOG.trace("Adding session cookie {}/{}", cookieName, sessionId);
            cookieEncoder.addCookie(cookieName, sessionId);

            response.setHeader(Names.SET_COOKIE, cookieEncoder.encode());

            response.setContent(content);

            final ChannelFuture future = event.getFuture();
            Channels.write(context, future, response, event.getRemoteAddress());

            if (HttpHeaders.isKeepAlive(attachment.getRequest())) {
                LOG.trace("Http request was marked keep-alive, not closing the connection.");
            } else {
                LOG.trace("Http request was not marked as keep-alive, closing connection...");
                future.addListener(ChannelFutureListener.CLOSE);
            }
        } else {
            context.sendDownstream(event);
        }
    }

    /**
     * Internal attachment which can be used to pass an {@link HttpRequest} and
     * a set of {@link Cookie}s as an attachment using {@link ChannelHandlerContext#setAttachment(Object)}.
     *
     * @since 1.0
     * @author Willi Schoenborn
     */
    private static final class Attachment {

        private final HttpRequest request;

        private final Set<Cookie> cookies;

        public Attachment(HttpRequest request, Set<Cookie> cookies) {
            this.request = request;
            this.cookies = cookies;
        }

        public HttpRequest getRequest() {
            return request;
        }

        public Set<Cookie> getCookies() {
            return cookies;
        }

    }

}