Package com.s3auth.relay

Source Code of com.s3auth.relay.HttpRequest

/**
* Copyright (c) 2012-2014, s3auth.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. 3) Neither the name of the s3auth.com nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.s3auth.relay;

import com.google.common.collect.Lists;
import com.jcabi.aspects.Loggable;
import com.jcabi.aspects.Tv;
import com.s3auth.hosts.Range;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang3.StringUtils;

/**
* HTTP request.
*
* <p>This class helps us to consume a HTTP request from an IO socket, and
* parse its content. This is how it can be used (socket should be opened):
*
* <pre>
* HttpRequest req = new HttpRequest(socket);
* String type = req.headers().get("Accept").iterator().next();
* URI uri = req.requestUri();
* </pre>
*
* <p>We don't support any other methods except "GET".
*
* <p>The class is mutable and thread-safe.
*
* @author Yegor Bugayenko (yegor@tpc2.com)
* @version $Id$
* @since 0.0.1
* @see HttpThread
*/
@ToString
@EqualsAndHashCode(of = { "mtd", "uri", "hdrs" })
@Loggable(Loggable.DEBUG)
@SuppressWarnings("PMD.UseConcurrentHashMap")
final class HttpRequest {

    /**
     * Range HTTP header.
     * @see <a href="HTTP headers">http://en.wikipedia.org/wiki/List_of_HTTP_header_fields</a>
     */
    private static final String RANGE_HEADER = "Range";

    /**
     * Range header matching pattern.
     */
    private static final Pattern RANGE_PATTERN =
        Pattern.compile("bytes=(\\d+)-(\\d+)?");

    /**
     * TOP line pattern.
     */
    private static final Pattern TOP =
        Pattern.compile("(GET|POST|PUT|OPTIONS) (/[^ ]*) HTTP/1\\.(0|1)");

    /**
     * Header line pattern.
     */
    private static final Pattern HEADER =
        Pattern.compile("([a-zA-Z][a-zA-Z\\-]*):\\s*(.*)\\s*");

    /**
     * Query param pattern.
     */
    private static final Pattern PARAMS =
        Pattern.compile("[\\?&]([a-zA-Z0-9][a-zA-Z0-9\\-]*)(=([a-zA-Z0-9]+))?");

    /**
     * HTTP mtd.
     */
    private final transient String mtd;

    /**
     * URI requested.
     */
    private final transient URI uri;

    /**
     * HTTP headers.
     */
    private final transient Map<String, Collection<String>> hdrs;

    /**
     * HTTP query params.
     */
    private final transient Map<String, Collection<String>> parms;

    /**
     * Public ctor.
     *
     * <p>It's important NOT to close reader in this mtd. If it's closed
     * here the entire socket gets closed.
     *
     * @param socket Socket to read from
     * @throws IOException If some socket problem
     * @see <a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol">HTTP</a>
     */
    HttpRequest(@NotNull final Socket socket) throws IOException {
        final BufferedReader reader = new BufferedReader(
            new InputStreamReader(socket.getInputStream(), Charsets.UTF_8)
        );
        final String top = reader.readLine();
        if (top == null) {
            throw new HttpException(
                HttpURLConnection.HTTP_BAD_REQUEST,
                "empty request"
            );
        }
        final Matcher matcher = HttpRequest.TOP.matcher(top);
        if (!matcher.matches()) {
            throw new HttpException(
                HttpURLConnection.HTTP_BAD_REQUEST,
                String.format("invalid first line: '%s'", top)
            );
        }
        this.parms = Collections.unmodifiableMap(this.parseParameters(top));
        if (!"GET".equals(matcher.group(1))) {
            throw new HttpException(
                HttpURLConnection.HTTP_BAD_METHOD,
                "only GET mtd is supported"
            );
        }
        this.mtd = matcher.group(1);
        this.uri = URI.create(matcher.group(2));
        final Collection<String> headers = new LinkedList<String>();
        while (true) {
            final String line = reader.readLine();
            if (line == null) {
                break;
            }
            if (line.isEmpty()) {
                break;
            }
            headers.add(line);
        }
        this.hdrs = Collections.unmodifiableMap(this.parseHeaders(headers));
    }

    /**
     * Get all found HTTP headers. Note that the returned map is unmodifiable.
     * @return Headers
     */
    public Map<String, Collection<String>> headers() {
        return Collections.unmodifiableMap(this.hdrs);
    }

    /**
     * Get all found HTTP parameters.
     * Note that the returned map is unmodifiable.
     * @return Headers
     */
    public Map<String, Collection<String>> parameters() {
        return Collections.unmodifiableMap(this.parms);
    }

    /**
     * Get URI requested.
     * @return The URI
     */
    public URI requestUri() {
        return this.uri;
    }

    /**
     * Get HTTP method requested.
     * @return The method
     */
    public String method() {
        return this.mtd;
    }

    /**
     * Get range requested.
     * @return The URI
     * @throws HttpException If something is wrong
     * @see <a href="http://en.wikipedia.org/wiki/Byte_serving">Byte Serving</a>
     */
    public Range range() throws HttpException {
        final Range range;
        if (this.hdrs.containsKey(HttpRequest.RANGE_HEADER)) {
            final Matcher matcher = HttpRequest.RANGE_PATTERN.matcher(
                this.hdrs.get(HttpRequest.RANGE_HEADER).iterator().next()
            );
            if (!matcher.matches()) {
                throw new HttpException(
                    HttpURLConnection.HTTP_BAD_REQUEST,
                    "invalid Range header format"
                );
            }
            final long last;
            if (matcher.group(2) == null) {
                last = Long.MAX_VALUE;
            } else {
                last = Long.parseLong(matcher.group(2));
            }
            range = new Range.Simple(Long.parseLong(matcher.group(1)), last);
        } else {
            range = Range.ENTIRE;
        }
        return range;
    }

    /**
     * Parse header lines and create full map.
     * @param lines All lines
     * @return Map of headers
     * @throws HttpException If some socket problem
     */
    private Map<String, Collection<String>> parseHeaders(
        final Iterable<String> lines) throws HttpException {
        final Map<String, Collection<String>> map =
            new CaseInsensitiveMap<String, Collection<String>>();
        for (final String line : lines) {
            final Matcher matcher = HttpRequest.HEADER.matcher(line);
            if (!matcher.matches()) {
                throw new HttpException(
                    HttpURLConnection.HTTP_BAD_REQUEST,
                    String.format("invalid header line: '%s'", line)
                );
            }
            final String name =
                matcher.group(1).trim().toLowerCase(Locale.ENGLISH);
            if (!map.containsKey(name)) {
                map.put(name, Lists.<String>newLinkedList());
            }
            map.get(name).add(matcher.group(2));
        }
        return map;
    }

    /**
     * Parse query parameters from request string and create full map.
     * @param request Request string
     * @return Map of headers
     */
    private Map<String, Collection<String>> parseParameters(
        final CharSequence request) {
        final Map<String, Collection<String>> map =
            new HashMap<String, Collection<String>>(0);
        final Matcher matcher = HttpRequest.PARAMS.matcher(request);
        while (matcher.find()) {
            final String name = matcher.group(1).trim();
            if (!map.containsKey(name)) {
                map.put(name, Lists.<String>newLinkedList());
            }
            map.get(name).add(
                StringUtils.defaultString(matcher.group(Tv.THREE))
            );
        }
        return map;
    }

}
TOP

Related Classes of com.s3auth.relay.HttpRequest

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.