All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.stylesmile.server.Request Maven / Gradle / Ivy

package io.github.stylesmile.server;

import io.github.stylesmile.file.LimitedInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.*;

import static io.github.stylesmile.server.JdkHTTPServer.*;

/**
 * The {@code Request} class encapsulates a single HTTP request.
 */
public class Request {

    public String method;
    protected URI uri;
    protected URL baseURL; // cached value
    protected String version;
    protected Headers headers;
    protected InputStream body;
    protected Socket sock;
    protected Map params; // cached value
    protected JdkHTTPServer.VirtualHost host; // cached value
    protected JdkHTTPServer.VirtualHost.ContextInfo context; // cached value

    /**
     * Constructs a Request from the data in the given input stream.
     *
     * @param in   the input stream from which the request is read
     * @param sock the underlying connected socket
     * @throws IOException if an error occurs
     */
    public Request(InputStream in, Socket sock) throws IOException {
        this.sock = sock;
        readRequestLine(in);
        headers = readHeaders(in);
        // RFC2616#3.6 - if "chunked" is used, it must be the last one
        // RFC2616#4.4 - if non-identity Transfer-Encoding is present,
        // it must either include "chunked" or close the connection after
        // the body, and in any case ignore Content-Length.
        // if there is no such Transfer-Encoding, use Content-Length
        // if neither header exists, there is no body
        String header = headers.get("Transfer-Encoding");
        if (header != null && !header.toLowerCase(Locale.US).equals("identity")) {
            if (Arrays.asList(splitElements(header, true)).contains("chunked"))
                body = new JdkHTTPServer.ChunkedInputStream(in, headers);
            else
                body = in; // body ends when connection closes
        } else {
            header = headers.get("Content-Length");
            long len = header == null ? 0 : parseULong(header, 10);
            body = new LimitedInputStream(in, len, false);
        }
    }

    /**
     * Returns the request method.
     *
     * @return the request method
     */
    public String getMethod() {
        return method;
    }

    /**
     * Returns the request URI.
     *
     * @return the request URI
     */
    public URI getURI() {
        return uri;
    }

    /**
     * Returns the request version string.
     *
     * @return the request version string
     */
    public String getVersion() {
        return version;
    }

    /**
     * Returns the request headers.
     *
     * @return the request headers
     */
    public Headers getHeaders() {
        return headers;
    }
    /**
     * Returns the input stream containing the request body.
     *
     * @return the input stream containing the request body
     */
    public InputStream getBody() {
        return body;
    }

    /**
     * Returns the underlying socket, which can be used to retrieve connection meta-data.
     *
     * @return the underlying socket
     */
    public Socket getSocket() {
        return sock;
    }

    /**
     * Returns the path component of the request URI, after
     * URL decoding has been applied (using the UTF-8 charset).
     *
     * @return the decoded path component of the request URI
     */
    public String getPath() {
        return uri.getPath();
    }

    /**
     * Sets the path component of the request URI. This can be useful
     * in URL rewriting, etc.
     *
     * @param path the path to set
     * @throws IllegalArgumentException if the given path is malformed
     */
    public void setPath(String path) {
        try {
            uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
                    trimDuplicates(path, '/'), uri.getQuery(), uri.getFragment());
            context = null; // clear cached context so it will be recalculated
        } catch (URISyntaxException use) {
            throw new IllegalArgumentException("error setting path", use);
        }
    }

    /**
     * Returns the base URL (scheme, host and port) of the request resource.
     * The host name is taken from the request URI or the Host header or a
     * default host (see RFC2616#5.2).
     *
     * @return the base URL of the requested resource, or null if it
     * is malformed
     */
    public URL getBaseURL() {
        if (baseURL != null)
            return baseURL;
        // normalize host header
        String host = uri.getHost();
        if (host == null) {
            host = headers.get("Host");
            if (host == null) // missing in HTTP/1.0
                host = detectLocalHostName();
        }
        int pos = host.indexOf(':');
        host = pos < 0 ? host : host.substring(0, pos);
        try {
            return baseURL = new URL(JdkHTTPServer.secure ? "https" : "http", host, JdkHTTPServer.port, "");
        } catch (MalformedURLException mue) {
            return null;
        }
    }

    /**
     * Returns the request parameters, which are parsed both from the query
     * part of the request URI, and from the request body if its content
     * type is "application/x-www-form-urlencoded" (i.e. a submitted form).
     * UTF-8 encoding is assumed in both cases.
     * 

* The parameters are returned as a list of string arrays, each containing * the parameter name as the first element and its corresponding value * as the second element (or an empty string if there is no value). *

* The list retains the original order of the parameters. * * @return the request parameters name-value pairs, * or an empty list if there are none * @throws IOException if an error occurs * @see JdkHTTPServer#parseParamsList(String) */ private List _paramsList; //noear,20210801 public List getParamsList() throws IOException { if (_paramsList == null) { List queryParams = parseParamsList(uri.getRawQuery()); List bodyParams = Collections.emptyList(); String ct = headers.get("Content-Type"); if (ct != null && ct.toLowerCase(Locale.US).startsWith("application/x-www-form-urlencoded")) bodyParams = parseParamsList(readToken(body, -1, "UTF-8", MAX_BODY_SIZE)); // 2MB limit _paramsList = new ArrayList<>(); //noear,20211218,最终都汇总 if (queryParams.isEmpty() == false) _paramsList.addAll(queryParams); if (bodyParams.isEmpty() == false) _paramsList.addAll(bodyParams); } return _paramsList; } /** * Returns the request parameters, which are parsed both from the query * part of the request URI, and from the request body if its content * type is "application/x-www-form-urlencoded" (i.e. a submitted form). * UTF-8 encoding is assumed in both cases. *

* For multivalued parameters (i.e. multiple parameters with the same * name), only the first one is considered. For access to all values, * use {@link #getParamsList()} instead. *

* The map iteration retains the original order of the parameters. * * @return the request parameters name-value pairs, * or an empty map if there are none * @throws IOException if an error occurs * @see #getParamsList() */ public Map getParams() throws IOException { if (params == null) { params = toMap(getParamsList()); } return params; } /** * Returns the absolute (zero-based) content range value read * from the Range header. If multiple ranges are requested, a single * range containing all of them is returned. * * @param length the full length of the requested resource * @return the requested range, or null if the Range header * is missing or invalid */ public long[] getRange(long length) { String header = headers.get("Range"); return header == null || !header.startsWith("bytes=") ? null : parseRange(header.substring(6), length); } /** * Reads the request line, parsing the method, URI and version string. * * @param in the input stream from which the request line is read * @throws IOException if an error occurs or the request line is invalid */ protected void readRequestLine(InputStream in) throws IOException { // RFC2616#4.1: should accept empty lines before request line // RFC2616#19.3: tolerate additional whitespace between tokens String line; try { do { line = readLine(in); } while (line.length() == 0); } catch (IOException ioe) { // if EOF, timeout etc. throw new IOException("missing request line"); // signal that the request did not begin } String[] tokens = split(line, " ", -1); if (tokens.length != 3) throw new IOException("invalid request line: \"" + line + "\""); try { method = tokens[0]; // must remove '//' prefix which constructor parses as host name uri = new URI(tokens[1]); //todo: by noear 20220707 new URI(trimDuplicates(tokens[1], '/')); version = tokens[2]; // RFC2616#2.1: allow implied LWS; RFC7230#3.1.1: disallow it } catch (URISyntaxException use) { throw new IOException("invalid URI: " + use.getMessage()); } } /** * Returns the virtual host corresponding to the requested host name, * or the default host if none exists. * * @return the virtual host corresponding to the requested host name, * or the default virtual host */ public JdkHTTPServer.VirtualHost getVirtualHost() { return host != null ? host : (host = JdkHTTPServer.getVirtualHost(getBaseURL().getHost())) != null ? host : (host = JdkHTTPServer.getVirtualHost(null)); } /** * Returns the info of the context handling this request. * * @return the info of the context handling this request, or an empty context */ public JdkHTTPServer.VirtualHost.ContextInfo getContext() { return context != null ? context : (context = getVirtualHost().getContext(getPath())); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy