com.feilong.servlet.http.RequestUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.feilong.servlet.http.RequestUtil.java

Source

/*
 * Copyright (C) 2008 feilong
 *
 * Licensed 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 com.feilong.servlet.http;

import static com.feilong.servlet.http.HttpHeaders.ORIGIN;
import static com.feilong.servlet.http.HttpHeaders.PROXY_CLIENT_IP;
import static com.feilong.servlet.http.HttpHeaders.REFERER;
import static com.feilong.servlet.http.HttpHeaders.USER_AGENT;
import static com.feilong.servlet.http.HttpHeaders.WL_PROXY_CLIENT_IP;
import static com.feilong.servlet.http.HttpHeaders.X_FORWARDED_FOR;
import static com.feilong.servlet.http.HttpHeaders.X_REAL_IP;
import static com.feilong.servlet.http.HttpHeaders.X_REQUESTED_WITH;
import static com.feilong.servlet.http.HttpHeaders.X_REQUESTED_WITH_VALUE_AJAX;
import static java.util.Collections.emptyMap;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.feilong.core.CharsetType;
import com.feilong.core.bean.ConvertUtil;
import com.feilong.core.lang.StringUtil;
import com.feilong.core.util.EnumerationUtil;
import com.feilong.core.util.MapUtil;
import com.feilong.servlet.http.entity.RequestLogSwitch;
import com.feilong.tools.jsonlib.JsonUtil;

import static com.feilong.core.CharsetType.ISO_8859_1;
import static com.feilong.core.URIComponents.QUESTIONMARK;
import static com.feilong.core.URIComponents.SCHEME_HTTP;
import static com.feilong.core.URIComponents.SCHEME_HTTPS;
import static com.feilong.core.Validator.isNotNullOrEmpty;
import static com.feilong.core.Validator.isNullOrEmpty;
import static com.feilong.core.util.SortUtil.sortMapByKeyAsc;

/**
 * {@link javax.servlet.http.HttpServletRequest HttpServletRequest}.
 * 
 * <h3>{@link HttpServletRequest#getRequestURI() getRequestURI()}  {@link HttpServletRequest#getRequestURL() getRequestURL()}:</h3>
 * 
 * <blockquote>
 * <table border="1" cellspacing="0" cellpadding="4" summary="">
 * <tr style="background-color:#ccccff">
 * <th align="left"></th>
 * <th align="left"></th>
 * </tr>
 * <tr valign="top">
 * <td><span style="color:red">request.getRequestURI()</span></td>
 * <td>/feilong/requestdemo.jsp</td>
 * </tr>
 * <tr valign="top" style="background-color:#eeeeff">
 * <td><span style="color:red">request.getRequestURL()</span></td>
 * <td>http://localhost:8080/feilong/requestdemo.jsp</td>
 * </tr>
 * </table>
 * </blockquote>
 * 
 * 
 * <h3>requesturl:</h3>
 * 
 * <blockquote>
 * 
 * <ol>
 * <li>getServletContext().getRealPath("/") ???(windows"\",linux"/"),getPathInfo()"/".</li>
 * <li>getPathInfo()getPathTranslated()servleturl-pattern/*/aa/*pattern?,null.</li>
 * <li>servleturl-pattern*.xxpattern,getServletPath()getRequestURI()??ContextPath.</li>
 * </ol>
 * 
 * <table border="1" cellspacing="0" cellpadding="4" summary="">
 * <tr style="background-color:#ccccff">
 * <th align="left"></th>
 * <th align="left"></th>
 * </tr>
 * 
 * <tr valign="top">
 * <td>{@link HttpServletRequest#getContextPath()}</td>
 * <td>{@link HttpServletRequest#getContextPath()}</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>{@link HttpServletRequest#getPathInfo()}</td>
 * <td>Returns any extra path information associated with the URL the client sent when it made this request. <br>
 * Servlet?,QueryString?</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>{@link HttpServletRequest#getServletPath()}</td>
 * <td>web.xmlServlet</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>{@link HttpServletRequest#getPathTranslated()}</td>
 * <td>getServletContext().getRealPath("/") + getPathInfo()</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>{@link HttpServletRequest#getRequestURI()}</td>
 * <td>getContextPath() + getServletPath() + getPathInfo()</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>{@link HttpServletRequest#getRequestURL()}</td>
 * <td>getScheme() + "://" + getServerName() + ":" + getServerPort() + getRequestURI()</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>{@link HttpServletRequest#getQueryString()}</td>
 * <td>{@code &}?GET?<br>
 * Returns the query string that is contained in the request URL after the path. <br>
 * This method returns null if the URL does not have a query string. <br>
 * Same as the value of the CGI variable QUERY_STRING.</td>
 * </tr>
 * </table>
 * </blockquote>
 * 
 * 
 * <h3><a href="http://tomcat.apache.org/whichversion.html">Apache Tomcat Versions:</a></h3>
 * 
 * <p>
 * Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies. <br>
 * Different versions of Apache Tomcat are available for different versions of the Servlet and JSP specifications. <br>
 * The mapping between the specifications and the respective Apache Tomcat versions is:
 * </p>
 * 
 * <blockquote>
 * <table border="1" cellspacing="0" cellpadding="4" summary="">
 * <tr style="background-color:#ccccff">
 * <th align="left">Servlet Spec</th>
 * <th align="left">JSP Spec</th>
 * <th align="left">EL Spec</th>
 * <th align="left">WebSocket Spec</th>
 * <th align="left">Apache Tomcat version</th>
 * <th align="left">Actual release revision</th>
 * <th align="left">Support Java Versions</th>
 * </tr>
 * 
 * <tr valign="top">
 * <td>4.0</td>
 * <td>TBD (2.4?)</td>
 * <td>TBD (3.1?)</td>
 * <td>TBD (1.2?)</td>
 * <td>9.0.x</td>
 * <td>9.0.0.M8 (alpha)</td>
 * <td>8 and later</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>3.1</td>
 * <td>2.3</td>
 * <td>3.0</td>
 * <td>1.1</td>
 * <td>8.5.x</td>
 * <td>8.5.3</td>
 * <td>7 and later</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>3.1</td>
 * <td>2.3</td>
 * <td>3.0</td>
 * <td>1.1</td>
 * <td>8.0.x (superseded)</td>
 * <td>8.0.35 (superseded)</td>
 * <td>7 and later</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>3.0</td>
 * <td>2.2</td>
 * <td>2.2</td>
 * <td>1.1</td>
 * <td>7.0.x</td>
 * <td>7.0.70</td>
 * <td>6 and later<br>
 * (7 and later for WebSocket)</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>2.5</td>
 * <td>2.1</td>
 * <td>2.1</td>
 * <td>N/A</td>
 * <td>6.0.x</td>
 * <td>6.0.45</td>
 * <td>5 and later</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>2.4</td>
 * <td>2.0</td>
 * <td>N/A</td>
 * <td>N/A</td>
 * <td>5.5.x (archived)</td>
 * <td>5.5.36 (archived)</td>
 * <td>1.4 and later</td>
 * </tr>
 * 
 * <tr valign="top">
 * <td>2.3</td>
 * <td>1.2</td>
 * <td>N/A</td>
 * <td>N/A</td>
 * <td>4.1.x (archived)</td>
 * <td>4.1.40 (archived)</td>
 * <td>1.3 and later</td>
 * </tr>
 * 
 * <tr valign="top" style="background-color:#eeeeff">
 * <td>2.2</td>
 * <td>1.1</td>
 * <td>N/A</td>
 * <td>N/A</td>
 * <td>3.3.x (archived)</td>
 * <td>3.3.2 (archived)</td>
 * <td>1.1 and later</td>
 * </tr>
 * 
 * </table>
 * </blockquote>
 * 
 * @author <a href="http://feitianbenyue.iteye.com/">feilong</a>
 * @see RequestAttributes
 * @see RequestLogSwitch
 * @since 1.0.0
 */
public final class RequestUtil {

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(RequestUtil.class);

    /** Don't let anyone instantiate this class. */
    private RequestUtil() {
        //AssertionError?. ?????. ???.
        //see Effective Java 2nd
        throw new AssertionError("No " + getClass().getName() + " instances for you!");
    }

    // ******************************??******************************************
    /**
     * ?????? (?:???,???).
     *
     * @param request
     *            
     * @param paramName
     *            ???
     * @return ??true,??false <br>
     *          <code>paramName</code> null, {@link NullPointerException}<br>
     *          <code>paramName</code> blank, {@link IllegalArgumentException}<br>
     * @see com.feilong.core.util.EnumerationUtil#contains(Enumeration, Object)
     * @since 1.4.0
     */
    public static boolean containsParam(HttpServletRequest request, String paramName) {
        Validate.notBlank(paramName, "paramName can't be null/empty!");
        return EnumerationUtil.contains(request.getParameterNames(), paramName);
    }

    /**
     * ? map(? TreeMap).
     * 
     * <p>
     * ?tomcatmap ?TreeMap ?,log;?<span style="color:red">map?</span>
     * </p>
     * 
     * <h3>tomcat getParameterMap() <span style="color:red">locked</span>(?):</h3>
     * 
     * <blockquote>
     * ?:tomcat , {@code org.apache.catalina.util#ParameterMap<K, V>},tomcat?,map?locked,<br>
     * <p>
     * ??map??.?,?,WebLogic,Tomcat,Resin,JBoss??.
     * </p>
     * ,??map?:
     * 
     * <ul>
     * <li>{@link Map#clear()}</li>
     * <li>{@link Map#put(Object, Object)}</li>
     * <li>{@link Map#putAll(Map)}</li>
     * <li>{@link Map#remove(Object)}</li>
     * </ul>
     * 
     * </blockquote>
     * 
     * @param request
     *            the request
     * @return the parameter map
     * @see "org.apache.catalina.connector.Request#getParameterMap()"
     */
    public static Map<String, String[]> getParameterMap(HttpServletRequest request) {
        // http://localhost:8888/s.htm?keyword&a=
        // ?  map key  keyword,a 

        return sortMapByKeyAsc(request.getParameterMap()); // servlet 3.0   Map<String, String[]>
    }

    /**
     * ? and ?map.
     * 
     * <p>
     *  j2ee{@link ServletRequest#getParameterMap()}map?,?(?notify/return request),???
     * </p>
     * 
     * @param request
     *            the request
     * @return the parameter single value map
     * @see #getParameterMap(HttpServletRequest)
     * @see MapUtil#toSingleValueMap(Map)
     * @since 1.2.0
     */
    public static Map<String, String> getParameterSingleValueMap(HttpServletRequest request) {
        return MapUtil.toSingleValueMap(getParameterMap(request));
    }

    /**
     *  {@link HttpServletRequest} ,??json? log(?log).
     * 
     * <p>
     *  {@link RequestLogSwitch#NORMAL}
     * </p>
     * 
     * <h3>:</h3>
     * <blockquote>
     * 
     * <pre class="code">
     * Map{@code <String, Object>} requestInfoMapForLog = RequestUtil.getRequestInfoMapForLog(request);}
     * LOGGER.debug("class:[{}],request info:{}", getClass().getSimpleName(), JsonUtil.format(requestInfoMapForLog);
     * </pre>
     * 
     * :
     * 
     * <pre class="code">
     * 19:28:37 DEBUG (AbstractWriteContentTag.java:63) execute() - class:[HttpConcatTag],request info: {
     * "requestFullURL": "/member/login.htm?a=b",
     * "request.getMethod": "GET",
     * "parameterMap": {"a": ["b"]}
     * }
     * </pre>
     * 
     * </blockquote>
     * 
     * @param request
     *            the request
     * @return the request string for log
     * @see RequestLogSwitch
     * @see #getRequestInfoMapForLog(HttpServletRequest, RequestLogSwitch)
     */
    public static Map<String, Object> getRequestInfoMapForLog(HttpServletRequest request) {
        return getRequestInfoMapForLog(request, RequestLogSwitch.NORMAL);
    }

    /**
     * request ,??json? log(?log).
     * 
     * <h3>:</h3>
     * <blockquote>
     * 
     * <pre class="code">
     * RequestLogSwitch requestLogSwitch = RequestLogSwitch.NORMAL;
     * Map{@code <String, Object>} requestInfoMapForLog = RequestUtil.getRequestInfoMapForLog(request, requestLogSwitch);
     * LOGGER.debug("class:[{}],request info:{}", getClass().getSimpleName(), JsonUtil.format(requestInfoMapForLog);
     * </pre>
     * 
     * :
     * 
     * <pre class="code">
     * 19:28:37 DEBUG (AbstractWriteContentTag.java:63) execute() - class:[HttpConcatTag],request info: {
     * "requestFullURL": "/member/login.htm?a=b",
     * "request.getMethod": "GET",
     * "parameterMap": {"a": ["b"]}
     * }
     * </pre>
     * 
     * </blockquote>
     * 
     * @param request
     *            the request
     * @param requestLogSwitch
     *            the request log switch
     * @return the request string for log
     * @see RequestLogBuilder#RequestLogBuilder(HttpServletRequest, RequestLogSwitch)
     */
    public static Map<String, Object> getRequestInfoMapForLog(HttpServletRequest request,
            RequestLogSwitch requestLogSwitch) {
        return new RequestLogBuilder(request, requestLogSwitch).build();
    }

    // ******************************* url? getAttribute*****************************************************.
    // [start] url?

    /**
     *  attribute.
     *
     * @param <T>
     *            the generic type
     * @param request
     *            the request
     * @param attributeName
     *            ??
     * @return  <code>attributeName</code> null, {@link NullPointerException}<br>
     *          <code>attributeName</code> blank, {@link IllegalArgumentException}<br>
     * @see javax.servlet.ServletRequest#getAttribute(String)
     * @since 1.3.0
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAttribute(HttpServletRequest request, String attributeName) {
        Validate.notBlank(attributeName, "attributeName can't be null/empty!");
        return (T) request.getAttribute(attributeName);
    }

    /**
     * ?request?,<span style="color:green">??? <code>klass</code></span>.
     *
     * @param <T>
     *            the generic type
     * @param request
     *            
     * @param name
     *            ??
     * @param klass
     *            ???
     * @return the attribute
     * @see com.feilong.core.bean.ConvertUtil#convert(Object, Class)
     * @see #getAttribute(HttpServletRequest, String)
     * @since 1.3.0
     */
    public static <T> T getAttribute(HttpServletRequest request, String name, Class<T> klass) {
        Object value = getAttribute(request, name);
        return ConvertUtil.convert(value, klass);
    }

    /**
     * ????.
     * <p>
     * <span style="color:red"> request ? forword</span>,forword?,? {@link RequestAttributes#FORWARD_REQUEST_URI}??
     * </p>
     * 
     * <pre class="code">
     * :http://localhost:8080/feilong/requestdemo.jsp?id=2
     * <b>:</b>http://localhost:8080/feilong/requestdemo.jsp
     * </pre>
     * 
     * :
     * 
     * <blockquote>
     * <table border="1" cellspacing="0" cellpadding="4" summary="">
     * <tr style="background-color:#ccccff">
     * <th align="left"></th>
     * <th align="left"></th>
     * </tr>
     * <tr valign="top">
     * <td><span style="color:red">request.getRequestURI()</span></td>
     * <td>/feilong/requestdemo.jsp</td>
     * </tr>
     * <tr valign="top" style="background-color:#eeeeff">
     * <td><span style="color:red">request.getRequestURL()</span></td>
     * <td>http://localhost:8080/feilong/requestdemo.jsp</td>
     * </tr>
     * </table>
     * </blockquote>
     * 
     * @param request
     *            the request
     * @return ????
     */
    public static String getRequestURL(HttpServletRequest request) {
        String forwardRequestUri = getAttribute(request, RequestAttributes.FORWARD_REQUEST_URI);
        return isNotNullOrEmpty(forwardRequestUri) ? forwardRequestUri : request.getRequestURL().toString();
    }

    /**
     * Return the servlet path for the given request, detecting an include request URL if called within a RequestDispatcher include.
     * 
     * @param request
     *            current HTTP request
     * @return the servlet path
     */
    public static String getOriginatingServletPath(HttpServletRequest request) {
        String servletPath = getAttribute(request, RequestAttributes.FORWARD_SERVLET_PATH);
        return isNotNullOrEmpty(servletPath) ? servletPath : request.getServletPath();
    }

    /**
     * ?.
     * 
     * <ul>
     * <li>request??queryString, requestURL(post)</li>
     * <li>request?queryString, requestURL+??queryString</li>
     * </ul>
     * 
     * @param request
     *            the request
     * @param charsetType
     *            ?, {@link CharsetType} ?
     * @return :http://localhost:8080/feilong/requestdemo.jsp?id=2
     */
    public static String getRequestFullURL(HttpServletRequest request, String charsetType) {
        String requestURL = getRequestURL(request);
        String queryString = request.getQueryString();
        return isNullOrEmpty(queryString) ? requestURL
                : requestURL + QUESTIONMARK + decodeISO88591String(queryString, charsetType);
    }

    /**
     * {@link CharsetType#ISO_8859_1} ??.
     * 
     * <p>
     * {@link CharsetType#ISO_8859_1} JAVA 
     * </p>
     * 
     * <h3>URI Encoding</h3>
     * 
     * <blockquote>
     * <ul>
     * <li>tomcat server.xml Connector URIEncoding="UTF-8"</li>
     * <li>{@code <fmt:requestEncodingvalue="UTF-8"/>}</li>
     * </ul>
     * </blockquote>
     *
     * @param str
     *            
     * @param charsetType
     *            ?, {@link CharsetType} ?
     * @return  <code>str</code> nullempty, {@link StringUtils#EMPTY}<br>
     * @see "org.apache.commons.codec.net.URLCodec#encode(String, String)"
     * @see "org.apache.taglibs.standard.tag.common.fmt.RequestEncodingSupport"
     * @see "org.apache.catalina.filters.SetCharacterEncodingFilter"
     * @since 1.7.3 move from feilong-core
     * @deprecated may delete
     */
    @Deprecated
    public static String decodeISO88591String(String str, String charsetType) {
        return StringUtil.newString(StringUtil.getBytes(str, ISO_8859_1), charsetType);
    }

    /**
     * scheme+serverName+port+getContextPath.
     * 
     * <p>
     *  http https.
     * <p>
     * 
     * @param request
     *            the request
     * @return :http://localhost:8080/feilong/
     * @see "org.apache.catalina.connector.Request#getRequestURL()"
     * @see "org.apache.catalina.realm.RealmBase#hasUserDataPermission(Request, Response, SecurityConstraint[])"
     * @see javax.servlet.http.HttpUtils#getRequestURL(HttpServletRequest)
     */
    public static String getServerRootWithContextPath(HttpServletRequest request) {
        String scheme = request.getScheme();
        int port = request.getServerPort() < 0 ? 80 : request.getServerPort();// Work around java.net.URL bug
        //*************************************************************************************
        StringBuilder sb = new StringBuilder();
        sb.append(scheme);
        sb.append("://");
        sb.append(request.getServerName());

        if ((scheme.equals(SCHEME_HTTP) && (port != 80)) || (scheme.equals(SCHEME_HTTPS) && (port != 443))) {
            sb.append(':');
            sb.append(port);
        }

        sb.append(request.getContextPath());
        return sb.toString();
    }

    // [end]

    /**
     * ? {@link RequestDispatcher} ??,Servlet??????.
     * 
     * <p>
     * Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server.<br>
     * This method allows one servlet to do preliminary processing of a request and another resource to generate the response.
     * </p>
     * 
     * <p>
     * For a <code>RequestDispatcher</code> obtained via <code>getRequestDispatcher()</code>, the <code>ServletRequest</code> object has its
     * path elements and parameters adjusted to match the path of the target resource.
     * </p>
     * 
     * <p>
     * <code>forward</code> should be called before the response has been committed to the client (before response body output has been
     * flushed). If the response already has been committed, this method throws an <code>IllegalStateException</code>. Uncommitted output in
     * the response buffer is automatically cleared before the forward.
     * </p>
     * 
     * <p>
     * The request and response parameters must be either the same objects as were passed to the calling servlet's service method or be
     * subclasses of the {@link ServletRequestWrapper} or {@link ServletResponseWrapper} classes that wrap them.
     * </p>
     * 
     * @param path
     *            the path
     * @param request
     *            a {@link ServletRequest} object that represents the request the client makes of the servlet
     * @param response
     *            a {@link ServletResponse} object,that represents the response the servlet returns to the client
     * @since 1.2.2
     */
    public static void forward(String path, HttpServletRequest request, HttpServletResponse response) {
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(path);
        try {
            requestDispatcher.forward(request, response);
        } catch (ServletException | IOException e) {
            LOGGER.error("", e);
            throw new RequestException(e);
        }
    }

    /**
     *  {@link RequestDispatcher} ??????,????.
     * 
     * <p>
     * Includes the content of a resource (servlet, JSP page,HTML file) in the response. <br>
     * In essence, this method enables programmatic server-side includes.
     * </p>
     * 
     * <p>
     * :?Servlet????????,???.<br>
     * The {@link ServletResponse} object has its path elements and parameters remain unchanged from the caller's. <br>
     * The included servlet cannot change the response status code or set headers; any attempt to make a change is ignored.
     * </p>
     * 
     * <p>
     * The request and response parameters must be either the same objects as were passed to the calling servlet's service method or be
     * subclasses of the {@link ServletRequestWrapper} or {@link ServletResponseWrapper} classes that wrap them.
     * </p>
     * 
     * @param path
     *            the path
     * @param request
     *            a {@link ServletRequest} object,that contains the client's request
     * @param response
     *            a {@link ServletResponse} object,that contains the servlet's response
     * @see javax.servlet.RequestDispatcher#include(ServletRequest, ServletResponse)
     * @see "org.springframework.web.servlet.ResourceServlet"
     * @since 1.2.2
     */
    public static void include(String path, HttpServletRequest request, HttpServletResponse response) {
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(path);
        try {
            requestDispatcher.include(request, response);
        } catch (ServletException | IOException e) {
            LOGGER.error("", e);
            throw new RequestException(e);
        }
    }

    // **************************Header*******************************************

    /**
     * ip?.
     * 
     * @param request
     *            the request
     * @return ip?
     * @see "org.apache.catalina.valves.RemoteIpValve"
     * @see <a href="http://distinctplace.com/infrastructure/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/">Story behind
     *      X-Forwarded-For and X-Real-IP headers</a>
     * @see <a href="http://lavafree.iteye.com/blog/1559183">nginx?CDN?ip</a>
     */
    public static String getClientIp(HttpServletRequest request) {
        // WL-Proxy-Client-IP=215.4.1.29
        // Proxy-Client-IP=215.4.1.29
        // X-Forwarded-For=215.4.1.29

        Map<String, String> map = new LinkedHashMap<>();

        String ipAddress = "";

        //ip header?, ? ( CDN ?cdn_real_ip),???
        //?,??
        String[] ipHeaderNames = { X_FORWARDED_FOR, X_REAL_IP, PROXY_CLIENT_IP, WL_PROXY_CLIENT_IP };

        //??
        for (String ipHeaderName : ipHeaderNames) {
            String ipHeaderValue = request.getHeader(ipHeaderName);//The header name is case insensitive (??)
            map.put(ipHeaderName, ipHeaderValue);
            if (isNotNullOrEmpty(ipHeaderValue) && !"unknown".equalsIgnoreCase(ipHeaderValue)) {
                ipAddress = ipHeaderValue;
                break;
            }
        }

        //,? request.getRemoteAddr()
        if (isNullOrEmpty(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            map.put("request.getRemoteAddr()", ipAddress);
        }

        // ?,IPIP,IP','
        if (ipAddress != null && ipAddress.indexOf(',') > 0) {
            //?????,X-Forwarded-For?,ip,??unknownIP. 
            ipAddress = ipAddress.substring(0, ipAddress.indexOf(','));
            map.put("firstIp", ipAddress);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("client real ips:{}", JsonUtil.format(map));
        }
        return ipAddress;
    }

    // *****************************Header**************************************

    /**
     * User Agent???, UA.
     * 
     * <p>
     * ,????CPU ??????????.
     * </p>
     * 
     * @param request
     *            the request
     * @return request?? {@link HttpHeaders#USER_AGENT} header,null
     * @see HttpHeaders#USER_AGENT
     */
    public static String getHeaderUserAgent(HttpServletRequest request) {
        return request.getHeader(USER_AGENT);
    }

    /**
     * URL.
     * 
     * <pre class="code">
     * ,http???,javascript
     * 
     * :
     * ?&lt;a href=&quot;url&quot;&gt;sss&lt;/a&gt;?
     * ?location&lt;a href=&quot;javascript:location='url'&quot;&gt;sss&lt;/a&gt;?
     * 
     * referer?????,??URL???,??a.html,
     * ?b.html,?b.html?a.htmlreferer??.
     * 
     * </pre>
     * 
     * @param request
     *            the request
     * @return request?? {@link HttpHeaders#REFERER} header,null
     * @see HttpHeaders#REFERER
     */
    public static String getHeaderReferer(HttpServletRequest request) {
        return request.getHeader(REFERER);
    }

    /**
     * 1?Origin????,? (,URL?). <br>
     * Referer?,Origin????URL,??. <br>
     * 2?Origin?POST,Referer.
     * 
     * @param request
     *            the request
     * @return request?? {@link HttpHeaders#ORIGIN} header,null
     * @see HttpHeaders#ORIGIN
     */
    public static String getHeaderOrigin(HttpServletRequest request) {
        return request.getHeader(ORIGIN);
    }

    /**
     * ?ajax.
     * 
     * @param request
     *            the request
     * @return ajax  true
     * @see "http://en.wikipedia.org/wiki/X-Requested-With#Requested-With"
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String header = request.getHeader(X_REQUESTED_WITH);
        return isNotNullOrEmpty(header) && header.equalsIgnoreCase(X_REQUESTED_WITH_VALUE_AJAX);
    }

    /**
     *  ,?ajax .
     * 
     * @param request
     *            the request
     * @return ?ajax true
     * @see #isAjaxRequest(HttpServletRequest)
     */
    public static boolean isNotAjaxRequest(HttpServletRequest request) {
        return !isAjaxRequest(request);
    }

    // *********************************************************************

    /**
     * ??requestattribute, name /attributeValue map(TreeMap).
     * 
     * 
     * <h3>?:</h3>
     * 
     * <blockquote>
     * <p style="color:red">
     * 1.??,json,?, {@link JsonUtil#formatSimpleMap(Map, Class...) }
     * </p>
     * 
     * <p>
     * 2.??mapremove?,??request Attribute
     * </p>
     * </blockquote>
     * 
     * @param request
     *            the request
     * @return {@link javax.servlet.ServletRequest#getAttributeNames()} nullempty,{@link Collections#emptyMap()}<br>
     */
    public static Map<String, Object> getAttributeMap(HttpServletRequest request) {
        Enumeration<String> attributeNames = request.getAttributeNames();
        if (isNullOrEmpty(attributeNames)) {
            return emptyMap();
        }

        Map<String, Object> map = new TreeMap<>();
        while (attributeNames.hasMoreElements()) {
            String name = attributeNames.nextElement();
            map.put(name, getAttribute(request, name));
        }
        return map;
    }

    // *********************************?********************************

    /**
     * request?.
     * 
     * @param request
     *            ?
     * @param paramName
     *            ???
     * @return request?
     */
    public static String getParameter(HttpServletRequest request, String paramName) {
        return request.getParameter(paramName);
    }
}