co.paralleluniverse.comsat.webactors.netty.NettyHttpRequest.java Source code

Java tutorial

Introduction

Here is the source code for co.paralleluniverse.comsat.webactors.netty.NettyHttpRequest.java

Source

/*
 * COMSAT
 * Copyright (c) 2015-2016, Parallel Universe Software Co. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
package co.paralleluniverse.comsat.webactors.netty;

import co.paralleluniverse.actors.ActorRef;
import co.paralleluniverse.comsat.webactors.Cookie;
import co.paralleluniverse.comsat.webactors.HttpRequest;
import co.paralleluniverse.comsat.webactors.HttpResponse;
import co.paralleluniverse.comsat.webactors.WebMessage;
import com.google.common.collect.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.UnsupportedCharsetException;
import java.util.*;

import static io.netty.handler.codec.http.HttpHeaders.Names.*;

/**
 * @author circlespainter
 */
public final class NettyHttpRequest extends HttpRequest {
    public static final String CHARSET_MARKER_STRING = "charset=";

    final ActorRef<? super HttpResponse> actorRef;
    final FullHttpRequest req;
    final ChannelHandlerContext ctx;
    final String sessionId;

    private static final Set<io.netty.handler.codec.http.cookie.Cookie> EMPTY_SET = new HashSet<>();

    private final ByteBuf reqContent;

    private InetSocketAddress sourceAddress;
    private ImmutableMultimap<String, String> params;
    private URI uri;
    private Collection<Cookie> cookies;
    private ListMultimap<String, String> heads;
    private ByteBuffer byteBufferBody;
    private String stringBody;
    private Charset encoding;
    private String contentType;

    public NettyHttpRequest(ActorRef<? super HttpResponse> actorRef, ChannelHandlerContext ctx, FullHttpRequest req,
            String sessionId) {
        this.actorRef = actorRef;
        this.ctx = ctx;
        this.req = req;
        this.sessionId = sessionId;

        reqContent = Unpooled.copiedBuffer(req.content());
    }

    @Override
    public final String getSourceHost() {
        fillSourceAddress();
        return sourceAddress != null ? sourceAddress.getHostString() : null;
    }

    @Override
    public final int getSourcePort() {
        fillSourceAddress();
        return sourceAddress != null ? sourceAddress.getPort() : -1;
    }

    private void fillSourceAddress() {
        final SocketAddress remoteAddress = ctx.channel().remoteAddress();
        if (sourceAddress == null && remoteAddress instanceof InetSocketAddress) {
            sourceAddress = (InetSocketAddress) remoteAddress;
        }
    }

    @Override
    public final Multimap<String, String> getParameters() {
        QueryStringDecoder queryStringDecoder;
        if (params == null) {
            queryStringDecoder = new QueryStringDecoder(req.getUri());
            final ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
            final Map<String, List<String>> parameters = queryStringDecoder.parameters();
            for (final String k : parameters.keySet())
                builder.putAll(k, parameters.get(k));
            params = builder.build();
        }
        return params;
    }

    @Override
    public final Map<String, Object> getAttributes() {
        return ImmutableMap.of(); // No attributes in Netty; Guava's impl. will return a pre-built instance
    }

    @Override
    public final String getScheme() {
        initUri();
        return uri.getScheme();
    }

    private void initUri() {
        if (uri == null) {
            try {
                uri = new URI(req.getUri());
            } catch (final URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public final String getMethod() {
        return req.getMethod().name();
    }

    @Override
    public final String getPathInfo() {
        initUri();
        return uri.getPath();
    }

    @Override
    public final String getContextPath() {
        return "/"; // Context path makes sense only for servlets
    }

    @Override
    public final String getQueryString() {
        initUri();
        return uri.getQuery();
    }

    @Override
    public final String getRequestURI() {
        return req.getUri();
    }

    @Override
    public final String getServerName() {
        initUri();
        return uri.getHost();
    }

    @Override
    public final int getServerPort() {
        initUri();
        return uri.getPort();
    }

    @SuppressWarnings("unchecked")
    @Override
    public final ActorRef<WebMessage> getFrom() {
        return (ActorRef<WebMessage>) actorRef;
    }

    @Override
    public final ListMultimap<String, String> getHeaders() {
        if (heads == null) {
            heads = extractHeaders(req.headers());
        }
        return heads;
    }

    @Override
    public final Collection<Cookie> getCookies() {
        if (cookies == null) {
            final ImmutableList.Builder<Cookie> builder = ImmutableList.builder();
            for (io.netty.handler.codec.http.cookie.Cookie c : getNettyCookies(req)) {
                builder.add(Cookie.cookie(c.name(), c.value()).setDomain(c.domain()).setPath(c.path())
                        .setHttpOnly(c.isHttpOnly()).setMaxAge((int) c.maxAge()).setSecure(c.isSecure()).build());
            }
            cookies = builder.build();
        }
        return cookies;
    }

    static Set<io.netty.handler.codec.http.cookie.Cookie> getNettyCookies(FullHttpRequest req) {
        final HttpHeaders heads = req.headers();
        final String head = heads != null ? heads.get(HttpHeaders.Names.COOKIE) : null;
        if (head != null)
            return ServerCookieDecoder.LAX.decode(head);
        else
            return EMPTY_SET;
    }

    @Override
    public final int getContentLength() {
        final String stringBody = getStringBody();
        if (stringBody != null)
            return stringBody.length();
        final ByteBuffer bufferBody = getByteBufferBody();
        if (bufferBody != null)
            return bufferBody.remaining();
        return 0;
    }

    @Override
    public final Charset getCharacterEncoding() {
        if (encoding == null)
            encoding = extractCharacterEncoding(getHeaders());
        return encoding;
    }

    @Override
    public final String getContentType() {
        if (contentType == null) {
            getHeaders();
            if (heads != null) {
                final List<String> cts = heads.get(CONTENT_TYPE);
                if (cts != null && cts.size() > 0)
                    contentType = cts.get(0);
            }
        }
        return null;
    }

    @Override
    public final String getStringBody() {
        if (stringBody == null) {
            if (byteBufferBody != null)
                return null;
            decodeStringBody();
        }
        return stringBody;
    }

    @Override
    public final ByteBuffer getByteBufferBody() {
        if (byteBufferBody == null) {
            if (stringBody != null)
                return null;
            if (reqContent != null)
                byteBufferBody = reqContent.nioBuffer();
        }
        return byteBufferBody;
    }

    public final String getSessionId() {
        return sessionId;
    }

    public ChannelHandlerContext getContext() {
        return ctx;
    }

    public FullHttpRequest getRequest() {
        return req;
    }

    private String decodeStringBody() {
        if (reqContent != null) {
            try {
                stringBody = getCharacterEncodingOrDefault().newDecoder().onMalformedInput(CodingErrorAction.REPORT)
                        .onUnmappableCharacter(CodingErrorAction.REPORT).decode(reqContent.nioBuffer()).toString();
            } catch (CharacterCodingException ignored) {
            }
        }
        return stringBody;
    }

    Charset getCharacterEncodingOrDefault() {
        return getCharacterEncodingOrDefault(getCharacterEncoding());
    }

    static Charset extractCharacterEncodingOrDefault(HttpHeaders headers) {
        return getCharacterEncodingOrDefault(extractCharacterEncoding(extractHeaders(headers)));
    }

    static Charset extractCharacterEncoding(ListMultimap<String, String> heads) {
        if (heads != null) {
            final List<String> cts = heads.get(CONTENT_TYPE);
            if (cts != null && cts.size() > 0) {
                final String ct = cts.get(0).trim().toLowerCase();
                if (ct.contains(CHARSET_MARKER_STRING)) {
                    try {
                        return Charset.forName(
                                ct.substring(ct.indexOf(CHARSET_MARKER_STRING) + CHARSET_MARKER_STRING.length())
                                        .trim());
                    } catch (UnsupportedCharsetException ignored) {
                    }
                }
            }
        }
        return null;
    }

    static ImmutableListMultimap<String, String> extractHeaders(HttpHeaders headers) {
        if (headers != null) {
            final ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
            for (final String n : headers.names())
                // Normalize header names by their conversion to lower case
                builder.putAll(n.toLowerCase(Locale.ENGLISH), headers.getAll(n));
            return builder.build();
        }
        return null;
    }

    private static Charset getCharacterEncodingOrDefault(Charset characterEncoding) {
        if (characterEncoding == null)
            return Charset.defaultCharset();
        return characterEncoding;
    }
}