holon.internal.http.netty.NettyRequestContext.java Source code

Java tutorial

Introduction

Here is the source code for holon.internal.http.netty.NettyRequestContext.java

Source

/**
 * Copyright (c) 2002-2014 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package holon.internal.http.netty;

import holon.api.exception.HolonException;
import holon.api.http.Content;
import holon.api.http.Cookie;
import holon.api.http.Cookies;
import holon.api.http.Request;
import holon.api.http.RequestHeaders;
import holon.api.http.Status;
import holon.internal.http.common.StandardCookie;
import holon.internal.routing.path.Path;
import holon.spi.RequestContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder;

import java.io.IOException;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE;

public class NettyRequestContext implements RequestContext {
    private final NettyCookies cookies = new NettyCookies();
    private final NettyOutput output = new NettyOutput();
    private final NettyRequestHeaders headers = new NettyRequestHeaders();

    private final Map<String, String> responseHeaders = new HashMap<>();
    private final Map<String, holon.api.http.Cookie> responseCookies = new HashMap<>();
    private final Set<String> discardCookies = new HashSet<>();

    private Path path;
    private HttpRequest request;
    private Channel channel;
    private Map<String, Object> formData;
    private boolean contentTypeOverridden;
    private boolean cookiesDecoded = false;
    private Map<String, List<String>> queryParams;

    /** Initialization called by the Holon stack */
    @Override
    public final RequestContext initialize(Path path) {
        this.path = path;
        return this;
    }

    /** Initialization called by the netty engine */
    public NettyRequestContext initialize(HttpRequest request, Channel channel, Map<String, Object> formData) {
        this.request = request;
        this.channel = channel;
        this.formData = formData;
        this.cookiesDecoded = false;
        this.contentTypeOverridden = false;
        this.queryParams = null;
        this.headers.initialize(request.headers());
        responseHeaders.clear();
        responseCookies.clear();
        discardCookies.clear();
        return this;
    }

    @Override
    public Path path() {
        return path;
    }

    @Override
    public Map<String, Object> formData() throws IOException {
        return formData;
    }

    @Override
    public RequestHeaders headers() {
        return headers;
    }

    @Override
    public Cookies cookies() {
        if (!cookiesDecoded) {
            cookiesDecoded = true;
            String value = request.headers().get(COOKIE);
            if (value == null) {
                cookies.initialize(null);
            } else {
                cookies.initialize(CookieDecoder.decode(value));
            }
        }
        return cookies;
    }

    @Override
    public Map<String, Iterable<String>> queryParams() {
        if (queryParams == null) {
            QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri());
            queryParams = decoderQuery.parameters();
        }
        return (Map) queryParams;
    }

    @Override
    public void respond(Status status) {
        respond(status, null, null);
    }

    @Override
    public void respond(Status status, Content content) {
        respond(status, content, null);
    }

    @Override
    public void respond(Status status, Content content, Object context) {
        ByteBuf buffer = renderContent(content, context);

        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.valueOf(status.code()), buffer);

        renderHeaders(response, contentType(content, context));
        renderCookies(response);

        response.headers().set(CONTENT_LENGTH, buffer.readableBytes());

        channel.writeAndFlush(response);
    }

    @Override
    public SocketAddress remoteAddress() {
        return channel.remoteAddress();
    }

    private ByteBuf renderContent(Content content, Object context) {
        try {
            if (content == null) {
                return Unpooled.EMPTY_BUFFER;
            }

            content.render(output.initialize(channel), context);
            return output.buffer();
        } catch (IOException e) {
            throw new HolonException("Failed to render response.", e);
        }
    }

    private void renderCookies(FullHttpResponse response) {
        for (Map.Entry<String, Cookie> cookieToAdd : this.responseCookies.entrySet()) {
            Cookie holonCookie = cookieToAdd.getValue();
            DefaultCookie cookie = new DefaultCookie(cookieToAdd.getKey(), cookieToAdd.getValue().value());

            cookie.setPath(holonCookie.path() == null ? "/" : holonCookie.path());
            cookie.setSecure(holonCookie.secure());
            cookie.setDomain(holonCookie.domain() == null ? "" : holonCookie.domain());
            cookie.setHttpOnly(holonCookie.httpOnly());

            response.headers().add("Set-Cookie", ServerCookieEncoder.encode(cookie));
        }
        for (String name : this.discardCookies) {
            DefaultCookie cookie = new DefaultCookie(name, "");
            cookie.setDiscard(true);
            response.headers().add("Set-Cookie", ServerCookieEncoder.encode(cookie));
        }
    }

    private void renderHeaders(FullHttpResponse response, String defaultContentType) {
        HttpHeaders headers = response.headers();
        for (Map.Entry<String, String> header : responseHeaders.entrySet()) {
            headers.add(header.getKey(), header.getValue());
        }
        if (!contentTypeOverridden) {
            headers.set(HttpHeaders.Names.CONTENT_TYPE, defaultContentType);
        }
    }

    @Override
    public Request addCookie(String name, String value) {
        return addCookie(name, value, null, null, -1, false, false);
    }

    @Override
    public Request addCookie(String name, String value, String path, String domain, int maxAge, boolean secure,
            boolean httpOnly) {
        responseCookies.put(name, new StandardCookie(name, value, domain, path, maxAge, secure, httpOnly));
        return this;
    }

    @Override
    public Request discardCookie(String name) {
        discardCookies.add(name);
        return this;
    }

    @Override
    public Request addHeader(String header, String value) {
        if (header.equalsIgnoreCase("Content-type")) {
            contentTypeOverridden = true;
        }
        responseHeaders.put(header, value);
        return this;
    }

    private String contentType(Content content, Object ctx) {
        if (content != null) {
            return content.contentType(ctx);
        } else {
            return "text/html";
        }
    }

    private static final class NettyRequestHeaders implements RequestHeaders {
        private HttpHeaders headers;

        @Override
        public String getFirst(String name) {
            return headers.get(name);
        }

        public NettyRequestHeaders initialize(HttpHeaders headers) {
            this.headers = headers;
            return this;
        }
    }
}