Package org.jboss.aerogear.io.netty.handler.codec.sockjs.transport

Source Code of org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports

/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you 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.jboss.aerogear.io.netty.handler.codec.sockjs.transport;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.buffer.Unpooled.unreleasableBuffer;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_CREDENTIALS;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaders.Names.ALLOW;
import static io.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
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.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.ServerCookieEncoder;
import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig;
import io.netty.util.CharsetUtil;

import java.util.Set;

/**
* Transports contains constants, enums, and utility methods that are
* common across transport implementations.
*/
public final class Transports {

    public static final String CONTENT_TYPE_PLAIN = "text/plain; charset=UTF-8";
    public static final String CONTENT_TYPE_JAVASCRIPT = "application/javascript; charset=UTF-8";
    public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
    public static final String CONTENT_TYPE_HTML = "text/html; charset=UTF-8";
    public static final String DEFAULT_COOKIE = "JSESSIONID=dummy;path=/";
    public static final String JSESSIONID = "JSESSIONID";
    private static final String NO_CACHE_HEADER =  "no-store, no-cache, must-revalidate, max-age=0";
    private static final ByteBuf NL = unreleasableBuffer(copiedBuffer("\n", CharsetUtil.UTF_8));

    public enum Type {
        WEBSOCKET,
        XHR,
        XHR_SEND,
        XHR_STREAMING,
        JSONP,
        JSONP_SEND,
        EVENTSOURCE,
        HTMLFILE;

        public String path() {
            return '/' + name().toLowerCase();
        }
    }

    private Transports() {
    }

    /**
     * Encodes the passes in requests JSESSIONID, if one exists, setting it to path of '/'.
     *
     * @param request the {@link HttpRequest} to parse for a JSESSIONID cookie.
     * @return {@code String} the encoded cookie or {@value Transports#DEFAULT_COOKIE} if no JSESSIONID cookie exits.
     */
    public static String encodeSessionIdCookie(final HttpRequest request) {
        final String cookieHeader = request.headers().get(HttpHeaders.Names.COOKIE);
        if (cookieHeader != null) {
            final Set<Cookie> cookies = CookieDecoder.decode(cookieHeader);
            for (Cookie c : cookies) {
                if (c.getName().equals(JSESSIONID)) {
                    c.setPath("/");
                    return ServerCookieEncoder.encode(c);
                }
            }
        }
        return DEFAULT_COOKIE;
    }

    /**
     * Writes the passed in String content to the response and also sets te content-type and content-lenght headaers.
     *
     * @param response the {@link FullHttpResponse} to write the content to.
     * @param content the content to be written.
     * @param contentType the content-type that will be set as the Content-Type Http response header.
     */
    public static void writeContent(final FullHttpResponse response, final String content, final String contentType) {
        final ByteBuf buf = copiedBuffer(content, CharsetUtil.UTF_8);
        response.headers().set(CONTENT_LENGTH, buf.readableBytes());
        response.content().writeBytes(buf);
        response.headers().set(CONTENT_TYPE, contentType);
        buf.release();
    }

    /**
     * Sets the following Http response headers
     * - SET_COOKIE  if {@link org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig#areCookiesNeeded()} is true
     * - CACHE_CONTROL to {@link Transports#setNoCacheHeaders(HttpResponse)}
     * - CORS Headers to {@link Transports#setCORSHeaders(HttpResponse)}
     *
     * @param response the Http Response.
     * @param config the SockJS configuration.
     */
    public static void setDefaultHeaders(final HttpResponse response, final SockJsConfig config) {
        if (config.areCookiesNeeded()) {
            response.headers().set(SET_COOKIE, DEFAULT_COOKIE);
        }
        setNoCacheHeaders(response);
        setCORSHeaders(response);
    }

    /**
     * Sets the following Http response headers
     * - SET_COOKIE  if {@link SockJsConfig#areCookiesNeeded()} is true, and uses the requests cookie.
     * - CACHE_CONTROL to {@link Transports#setNoCacheHeaders(HttpResponse)}
     * - CORS Headers to {@link Transports#setCORSHeaders(HttpResponse)}
     *
     * @param response the Http Response.
     * @param config the SockJS configuration.
     */
    public static void setDefaultHeaders(final FullHttpResponse response, final SockJsConfig config,
            final HttpRequest request) {
        if (config.areCookiesNeeded()) {
            response.headers().set(SET_COOKIE, encodeSessionIdCookie(request));
        }
        setNoCacheHeaders(response);
        setCORSHeaders(response);
    }

    /**
     * Sets the Http response header SET_COOKIE if {@link SockJsConfig#areCookiesNeeded()} is true.
     *
     * @param response the Http Response.
     * @param config the SockJS configuration.
     * @param request the Http request which will be inspected for the existence of a JSESSIONID cookie.
     */
    public static void setSessionIdCookie(final FullHttpResponse response, final SockJsConfig config,
            final HttpRequest request) {
        if (config.areCookiesNeeded()) {
            response.headers().set(SET_COOKIE, encodeSessionIdCookie(request));
        }
    }

    /**
     * Sets the Http response header CACHE_CONTROL to {@link Transports#NO_CACHE_HEADER}.
     *
     * @param response the Http response for which the CACHE_CONTROL header will be set.
     */
    public static void setNoCacheHeaders(final HttpResponse response) {
        response.headers().set(CACHE_CONTROL, NO_CACHE_HEADER);
    }

    /**
     * Sets the CORS Http response headers ACCESS_CONTROLL_ALLOW_ORIGIN to '*', and ACCESS_CONTROL_ALLOW_CREDENTIALS to
     * 'true".
     *
     * @param response the Http response for which the CORS headers will be set.
     */
    public static void setCORSHeaders(final HttpResponse response) {
        response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response.headers().set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    }

    /**
     * Will add an new line character to the passed in ByteBuf.
     *
     * @param buf the {@link ByteBuf} for which an '\n', new line, will be added.
     * @return {@code ByteBuf} a copied byte buffer with a '\n' appended.
     */
    public static ByteBuf wrapWithLN(final ByteBuf buf) {
        return copiedBuffer(buf, NL.duplicate());
    }

    /**
     * Escapes unicode characters in the passed in char array to a Java string with
     * Java style escaped charaters.
     *
     * @param value the char[] for which unicode characters should be escaped
     * @return {@code String} Java style escaped unicode characters.
     */
    public static String escapeCharacters(final char[] value) {
        final StringBuilder buffer = new StringBuilder();
        for (char ch : value) {
            if (ch >= '\u0000' && ch <= '\u001F' ||
                    ch >= '\uD800' && ch <= '\uDFFF' ||
                    ch >= '\u200C' && ch <= '\u200F' ||
                    ch >= '\u2028' && ch <= '\u202F' ||
                    ch >= '\u2060' && ch <= '\u206F' ||
                    ch >= '\uFFF0' && ch <= '\uFFFF') {
                final String ss = Integer.toHexString(ch);
                buffer.append('\\').append('u');
                for (int k = 0; k < 4 - ss.length(); k++) {
                    buffer.append('0');
                }
                buffer.append(ss.toLowerCase());
            } else {
                buffer.append(ch);
            }
        }
        return buffer.toString();
    }

    /**
     * Processes the input ByteBuf and escapes the any control characters, quotes, slashes,
     * and unicode characters.
     *
     * @param input the bytes of characters to process.
     * @param buffer the {@link ByteBuf} into which the result of processing will be added.
     * @return {@code ByteBuf} which is the same ByteBuf as passed in as the buffer param. This is done to
     *                         simplify method invocation where possible which might require a return value.
     */
    public static ByteBuf escapeJson(final ByteBuf input, final ByteBuf buffer) {
        final int count = input.readableBytes();
        for (int i = 0; i < count; i++) {
            final byte ch = input.getByte(i);
            switch(ch) {
                case '"': buffer.writeByte('\\').writeByte('\"'); break;
                case '/': buffer.writeByte('\\').writeByte('/'); break;
                case '\\': buffer.writeByte('\\').writeByte('\\'); break;
                case '\b': buffer.writeByte('\\').writeByte('b'); break;
                case '\f': buffer.writeByte('\\').writeByte('f'); break;
                case '\n': buffer.writeByte('\\').writeByte('n'); break;
                case '\r': buffer.writeByte('\\').writeByte('r'); break;
                case '\t': buffer.writeByte('\\').writeByte('t'); break;

                default:
                    // Reference: http://www.unicode.org/versions/Unicode5.1.0/
                    if (ch >= '\u0000' && ch <= '\u001F' ||
                            ch >= '\uD800' && ch <= '\uDFFF' ||
                            ch >= '\u200C' && ch <= '\u200F' ||
                            ch >= '\u2028' && ch <= '\u202F' ||
                            ch >= '\u2060' && ch <= '\u206F' ||
                            ch >= '\uFFF0' && ch <= '\uFFFF') {
                        final String ss = Integer.toHexString(ch);
                        buffer.writeByte('\\').writeByte('u');
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            buffer.writeByte('0');
                        }
                        buffer.writeBytes(ss.toLowerCase().getBytes());
                    } else {
                        buffer.writeByte(ch);
                    }
            }
        }
        return buffer;
    }

    /**
     * Creates a {@code FullHttpResponse} with the {@code METHOD_NOT_ALLOWED} status.
     *
     * @param version the HttpVersion to be used.
     * @return {@link FullHttpResponse} with the {@link HttpResponseStatus#METHOD_NOT_ALLOWED}.
     */
    public static FullHttpResponse methodNotAllowedResponse(final HttpVersion version) {
        final FullHttpResponse response = responseWithoutContent(version, METHOD_NOT_ALLOWED);
        response.headers().add(ALLOW, GET);
        return response;
    }

    /**
     * Creates a {@code FullHttpResponse} with the {@code BAD_REQUEST} status and a body.
     *
     * @param version the HttpVersion to be used.
     * @param content the content that will become the response body.
     * @return {@link FullHttpResponse} with the {@link HttpResponseStatus#BAD_REQUEST}.
     */
    public static FullHttpResponse badRequestResponse(final HttpVersion version, final String content) {
        return responseWithContent(version, BAD_REQUEST, CONTENT_TYPE_PLAIN, content);
    }

    /**
     * Creates a {@code FullHttpResponse} with the {@code INTERNAL_SERVER_ERROR} status and a body.
     *
     * @param version the HttpVersion to be used.
     * @param content the content that will become the response body.
     * @return {@link FullHttpResponse} with the {@link HttpResponseStatus#INTERNAL_SERVER_ERROR}.
     */
    public static FullHttpResponse internalServerErrorResponse(final HttpVersion version, final String content) {
        return responseWithContent(version, INTERNAL_SERVER_ERROR, CONTENT_TYPE_PLAIN, content);
    }

    /**
     * Sends a HttpResponse using the ChannelHandlerContext passed in.
     *
     * @param ctx the {@link ChannelHandlerContext} to use.
     * @param version the {@link HttpVersion} that the response should have.
     * @param status the status of the HTTP response
     * @param contentType the value for the 'Content-Type' HTTP response header.
     * @param content the content that will become the body of the HTTP response.
     * @param promise the {@link ChannelPromise}
     */
    public static void respond(final ChannelHandlerContext ctx,
            final HttpVersion version,
            final HttpResponseStatus status,
            final String contentType,
            final String content,
            final ChannelPromise promise) {
        final FullHttpResponse response = responseWithContent(version, status, contentType, content);
        writeResponse(ctx, response);
    }

    /**
     * Sends a HttpResponse using the ChannelHandlerContext passed in.
     *
     * @param ctx the {@link ChannelHandlerContext} to use.
     * @param version the {@link HttpVersion} that the response should have.
     * @param status the status of the HTTP response
     * @param contentType the value for the 'Content-Type' HTTP response header.
     * @param content the content that will become the body of the HTTP response.
     */
    public static void respond(final ChannelHandlerContext ctx,
            final HttpVersion version,
            final HttpResponseStatus status,
            final String contentType,
            final String content) {
        final FullHttpResponse response = responseWithContent(version, status, contentType, content);
        writeResponse(ctx, response);
    }

    /**
     * Creates FullHttpResponse without a response body.
     *
     * @param version the {@link HttpVersion} that the response should have.
     * @param status the status of the HTTP response
     */
    public static FullHttpResponse responseWithoutContent(final HttpVersion version, final HttpResponseStatus status) {
        final FullHttpResponse response = new DefaultFullHttpResponse(version, status);
        response.headers().set(CONTENT_LENGTH, 0);
        return response;
    }

    /**
     * Creates FullHttpResponse with a response body.
     *
     * @param version the {@link HttpVersion} that the response should have.
     * @param status the status of the HTTP response
     * @param contentType the value for the 'Content-Type' HTTP response header.
     * @param content the content that will become the body of the HTTP response.
     */
    public static FullHttpResponse responseWithContent(final HttpVersion version, final HttpResponseStatus status,
            final String contentType, final String content) {
        final FullHttpResponse response = new DefaultFullHttpResponse(version, status);
        writeContent(response, content, contentType);
        return response;
    }

    /**
     * Writes the passed in respone to the {@link ChannelHandlerContext} if it is active.
     *
     * @param ctx the {@link ChannelHandlerContext} to write the response to.
     * @param response the {@link HttpResponseStatus} to be written.
     */
    public static void writeResponse(final ChannelHandlerContext ctx, final HttpResponse response) {
        if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
            ctx.writeAndFlush(response);
        }
    }

    /**
     * Writes the passed in respone to the {@link ChannelHandlerContext} if it is active.
     *
     * @param ctx the {@link ChannelHandlerContext} to write the response to.
     * @param promise the {@link ChannelPromise}
     * @param response the {@link HttpResponseStatus} to be written.
     */
    public static void writeResponse(final ChannelHandlerContext ctx, final ChannelPromise promise,
            final HttpResponse response) {
        if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
            ctx.writeAndFlush(response, promise);
        }
    }
}
TOP

Related Classes of org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.