com.feilong.servlet.http.RequestUtil Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* 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.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.lang.StringUtil.EMPTY;
import static com.feilong.core.lang.StringUtil.tokenizeToStringArray;
import static com.feilong.core.util.MapUtil.newLinkedHashMap;
import static com.feilong.core.util.ResourceBundleUtil.getResourceBundle;
import static com.feilong.core.util.ResourceBundleUtil.getValue;
import static com.feilong.core.util.SortUtil.sortMapByKeyAsc;
import static com.feilong.servlet.http.HttpHeaders.ORIGIN;
import static com.feilong.servlet.http.HttpHeaders.REFERER;
import static com.feilong.servlet.http.HttpHeaders.USER_AGENT;
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.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.core.CharsetType;
import com.feilong.core.Validate;
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.io.ReaderUtil;
import com.feilong.json.JsonUtil;
import com.feilong.lib.lang3.StringUtils;
import com.feilong.servlet.http.entity.RequestLogSwitch;
/**
* {@link javax.servlet.http.HttpServletRequest HttpServletRequest}工具类.
*
* {@link HttpServletRequest#getRequestURI() getRequestURI()} 和 {@link HttpServletRequest#getRequestURL() getRequestURL()}:
*
*
*
*
* 字段
* 返回值
*
*
* request.getRequestURI()
* /feilong/requestdemo.jsp
*
*
* request.getRequestURL()
* http://localhost:8080/feilong/requestdemo.jsp
*
*
*
*
*
* 关于从request中获得相关路径和url:
*
*
*
*
* - getServletContext().getRealPath("/") 后包含当前系统的文件夹分隔符(windows系统是"\",linux系统是"/"),而getPathInfo()以"/"开头.
* - getPathInfo()与getPathTranslated()在servlet的url-pattern被设置为/*或/aa/*之类的pattern时才有值,其他时候都返回null.
* - 在servlet的url-pattern被设置为*.xx之类的pattern时,getServletPath()返回的是getRequestURI()去掉前面ContextPath的剩余部分.
*
*
*
*
* 字段
* 说明
*
*
*
* {@link HttpServletRequest#getContextPath()}
* {@link HttpServletRequest#getContextPath()}
*
*
*
* {@link HttpServletRequest#getPathInfo()}
* Returns any extra path information associated with the URL the client sent when it made this request.
* Servlet访问路径之后,QueryString之前的中间部分
*
*
*
* {@link HttpServletRequest#getServletPath()}
* web.xml中定义的Servlet访问路径
*
*
*
* {@link HttpServletRequest#getPathTranslated()}
* 等于getServletContext().getRealPath("/") + getPathInfo()
*
*
*
* {@link HttpServletRequest#getRequestURI()}
* 等于getContextPath() + getServletPath() + getPathInfo()
*
*
*
* {@link HttpServletRequest#getRequestURL()}
* 等于getScheme() + "://" + getServerName() + ":" + getServerPort() + getRequestURI()
*
*
*
* {@link HttpServletRequest#getQueryString()}
* {@code &}之后GET方法的参数部分
* Returns the query string that is contained in the request URL after the path.
* This method returns null if the URL does not have a query string.
* Same as the value of the CGI variable QUERY_STRING.
*
*
*
*
*
* Apache Tomcat Versions:
*
*
* Apache Tomcat™ is an open source software implementation of the Java Servlet and JavaServer Pages technologies.
* Different versions of Apache Tomcat are available for different versions of the Servlet and JSP specifications.
* The mapping between the specifications and the respective Apache Tomcat versions is:
*
*
*
*
*
* Servlet Spec
* JSP Spec
* EL Spec
* WebSocket Spec
* Apache Tomcat version
* Actual release revision
* Support Java Versions
*
*
*
* 4.0
* TBD (2.4?)
* TBD (3.1?)
* TBD (1.2?)
* 9.0.x
* 9.0.0.M8 (alpha)
* 8 and later
*
*
*
* 3.1
* 2.3
* 3.0
* 1.1
* 8.5.x
* 8.5.3
* 7 and later
*
*
*
* 3.1
* 2.3
* 3.0
* 1.1
* 8.0.x (superseded)
* 8.0.35 (superseded)
* 7 and later
*
*
*
* 3.0
* 2.2
* 2.2
* 1.1
* 7.0.x
* 7.0.70
* 6 and later
* (7 and later for WebSocket)
*
*
*
* 2.5
* 2.1
* 2.1
* N/A
* 6.0.x
* 6.0.45
* 5 and later
*
*
*
* 2.4
* 2.0
* N/A
* N/A
* 5.5.x (archived)
* 5.5.36 (archived)
* 1.4 and later
*
*
*
* 2.3
* 1.2
* N/A
* N/A
* 4.1.x (archived)
* 4.1.40 (archived)
* 1.3 and later
*
*
*
* 2.2
* 1.1
* N/A
* N/A
* 3.3.x (archived)
* 3.3.2 (archived)
* 1.1 and later
*
*
*
*
*
* @author feilong
* @see RequestAttributes
* @see RequestLogSwitch
* @since 1.0.0
*/
public final class RequestUtil{
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(RequestUtil.class);
//---------------------------------------------------------------
/**
* 存放到 request 作用域中的 requestbody 名字.
*
* @see #getRequestBody(HttpServletRequest)
* @since 1.14.2
*/
private static final String REQUEST_BODY_SCOPE_ATTRIBUTE_NAME = RequestUtil.class.getName() + ".REQUEST_BODY";
//---------------------------------------------------------------
/**
* 获得用户真实IP 循环的IP头.
*
* @since 3.0.0
*/
private static final String[] IP_HEADER_NAMES = tokenizeToStringArray(
getValue(getResourceBundle("config/feilong-request-clientIP-headers"), "clientIP.headerNames"),
",");
//---------------------------------------------------------------
/**
* 静态资源的后缀.
* {@value}
.
*
* @see com.feilong.io.entity.MimeType
* @since 1.12.0
* @since 3.0.0 change to config
*/
private static final String[] STATIC_RESOURCE_SUFFIX = tokenizeToStringArray(
getValue(getResourceBundle("config/feilong-request-staticResourceSuffix"), "request.staticResourceSuffix"),
",");
//---------------------------------------------------------------
/** Don't let anyone instantiate this class. */
private RequestUtil(){
//AssertionError不是必须的. 但它可以避免不小心在类的内部调用构造器. 保证该类在任何情况下都不会被实例化.
//see 《Effective Java》 2nd
throw new AssertionError("No " + getClass().getName() + " instances for you!");
}
//---------------------------------------------------------------
/**
* 判断请求是否是静态资源.
*
* @param requestURI
* the request URI
* @return 如果 requestURI
是null或者empty,返回 false
* 如果 requestURI
不包含.,返回 false
* 其他循环 {@link #STATIC_RESOURCE_SUFFIX} ,忽视大小写判断后缀
* @see HttpServletRequest#getRequestURI()
* @since 1.12.0
*/
public static boolean isStaticResource(String requestURI){
if (isNullOrEmpty(requestURI)){
return false;
}
//---------------------------------------------------------------
if (!requestURI.contains(".")){
return false;
}
//---------------------------------------------------------------
for (String uriSuffix : STATIC_RESOURCE_SUFFIX){
if (StringUtils.endsWithIgnoreCase(requestURI, uriSuffix)){
return true;
}
}
return false;
}
//---------------------------------------------------------------
/**
* 判断传入的method
是否在 支持的supportHttpMethods
数组中.
*
* @param supportHttpMethods
* 支持的method 数组
* @param method
* the method
* @return 如果 method
是null,抛出 {@link NullPointerException}
* 如果 method
是blank,抛出 {@link IllegalArgumentException}
* 如果 supportHttpMethods
是null或者empty,返回 false
* 循环 supportHttpMethods, 忽视大小写判断和 method 是否equalsIgnoreCase, 如果是返回true,否则false
* @since 1.12.1
*/
public static boolean isSupportMethod(String[] supportHttpMethods,String method){
Validate.notBlank(method, "method can't be blank!");
//---------------------------------------------------------------
//null 或者 empty 表示没有一个 method 支持的,不过滤
if (isNullOrEmpty(supportHttpMethods)){
return false;
}
//---------------------------------------------------------------
for (String supportHttpMethod : supportHttpMethods){
//如果当前的请求 method ,在支持的列表里面, 那么表示要过滤
if (StringUtils.equalsIgnoreCase(supportHttpMethod, method)){
return true;
}
}
return false;
}
//-------------------------是否包含--------------------------------------
/**
* 请求路径中是否包含某个参数名称 (注意:这是判断是否包含参数,而不是判断参数值是否为空).
*
* @param request
* 请求
* @param paramName
* 参数名称
* @return 包含该参数返回true,不包含返回false
* 如果 paramName
是null,抛出 {@link NullPointerException}
* 如果 paramName
是blank,抛出 {@link IllegalArgumentException}
* @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(结果转成了key自然排序的TreeMap).
*
*
* 此方式会将tomcat返回的map 转成TreeMap 返回,便于log; 也可以对这个返回的map进行操作
*
*
* tomcat getParameterMap() locked(只能读):
*
*
* 注意:tomcat 默认实现,返回的是 {@code org.apache.catalina.util#ParameterMap},tomcat返回之前,会将此map的状态设置为locked,
*
*
* 不像普通的map数据一样可以修改.
* 这是因为服务器为了实现一定的安全规范,所作的限制,WebLogic,Tomcat,Resin,JBoss等服务器均实现了此规范.
*
*
* 不能做以下的map操作:
*
*
* - {@link Map#clear()}
* - {@link Map#put(Object, Object)}
* - {@link Map#putAll(Map)}
* - {@link Map#remove(Object)}
*
*
*
*
* @param request
* the request
* @return the parameter map
* @see "org.apache.catalina.connector.Request#getParameterMap()"
*/
public static Map getParameterMap(HttpServletRequest request){
// http://localhost:8888/s.htm?keyword&a=
// 这种链接 map key 会是 keyword,a 值都是空
return sortMapByKeyAsc(request.getParameterMap()); // servlet 3.0 此处返回类型的是 泛型数组 Map
}
/**
* 获得请求参数和单值map.
*
*
* 由于调用的 {@link #getParameterMap(HttpServletRequest)}结果是有序的,
* 此方法返回的map也是有序的
*
*
*
* 由于j2ee{@link ServletRequest#getParameterMap()}返回的map值是数组形式,
* 对于一些确认是单值的请求(比如支付宝notify/return request),不便后续处理
*
*
* @param request
* the request
* @return the parameter single value map
* @see #getParameterMap(HttpServletRequest)
* @see MapUtil#toSingleValueMap(Map)
* @since 1.2.0
*/
public static Map getParameterSingleValueMap(HttpServletRequest request){
return MapUtil.toSingleValueMap(getParameterMap(request));
}
//---------------------------------------------------------------
/**
* 将 {@link HttpServletRequest} 相关属性,数据转成json格式 以便log显示(目前仅作log使用).
*
*
* 默认使用 {@link RequestLogSwitch#NORMAL}
*
*
* 示例:
*
*
*
* Map{@code } requestInfoMapForLog = RequestUtil.getRequestInfoMapForLog(request);}
* LOGGER.debug("class:[{}],request info:{}", getClass().getSimpleName(), JsonUtil.format(requestInfoMapForLog);
*
*
* 输出结果:
*
*
* 19:28:37 DEBUG (AbstractWriteContentTag.java:63) execute() - class:[HttpConcatTag],request info: {
* "requestFullURL": "/member/login.htm?a=b",
* "request.getMethod": "GET",
* "parameterMap": {"a": ["b"]}
* }
*
*
*
*
* @param request
* the request
* @return the request string for log
* @see RequestLogSwitch
* @see #getRequestInfoMapForLog(HttpServletRequest, RequestLogSwitch)
*/
public static Map getRequestInfoMapForLog(HttpServletRequest request){
return getRequestInfoMapForLog(request, RequestLogSwitch.NORMAL);
}
/**
* 将request 相关属性,数据转成json格式 以便log显示(目前仅作log使用).
*
* 示例:
*
*
*
* RequestLogSwitch requestLogSwitch = RequestLogSwitch.NORMAL;
* Map{@code } requestInfoMapForLog = RequestUtil.getRequestInfoMapForLog(request, requestLogSwitch);
* LOGGER.debug("class:[{}],request info:{}", getClass().getSimpleName(), JsonUtil.format(requestInfoMapForLog);
*
*
* 输出结果:
*
*
* 19:28:37 DEBUG (AbstractWriteContentTag.java:63) execute() - class:[HttpConcatTag],request info: {
* "requestFullURL": "/member/login.htm?a=b",
* "request.getMethod": "GET",
* "parameterMap": {"a": ["b"]}
* }
*
*
*
*
* @param request
* the request
* @param requestLogSwitch
* the request log switch
* @return the request string for log
* @see RequestLogBuilder#RequestLogBuilder(HttpServletRequest, RequestLogSwitch)
*/
public static Map getRequestInfoMapForLog(HttpServletRequest request,RequestLogSwitch requestLogSwitch){
return new RequestLogBuilder(request, requestLogSwitch).build();
}
//-------------------------url参数相关 getAttribute--------------------------------------
// [start] url参数相关
/**
* 获得 attribute.
*
* @param
* the generic type
* @param request
* the request
* @param attributeName
* 属性名称
* @return 如果 attributeName
是null,抛出 {@link NullPointerException}
* 如果 attributeName
是blank,抛出 {@link IllegalArgumentException}
* @see javax.servlet.ServletRequest#getAttribute(String)
* @since 1.3.0
*/
@SuppressWarnings("unchecked")
public static T getAttribute(HttpServletRequest request,String attributeName){
Validate.notBlank(attributeName, "attributeName can't be null/empty!");
return (T) request.getAttribute(attributeName);
}
/**
* 取到request里面的属性值,转换类型成指定的参数 klass
.
*
* @param
* 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 getAttribute(HttpServletRequest request,String name,Class klass){
Object value = getAttribute(request, name);
return ConvertUtil.convert(value, klass);
}
//---------------------------------------------------------------
/**
* 获得请求的?部分前面的地址.
*
* 自动识别 request 是否 forword,如果是forword过来的,那么取 {@link RequestAttributes#FORWARD_REQUEST_URI}变量
*
*
*
* 如:http://localhost:8080/feilong/requestdemo.jsp?id=2
* 返回:http://localhost:8080/feilong/requestdemo.jsp
*
*
* 注:
*
*
*
*
* 字段
* 返回值
*
*
* request.getRequestURI()
* /feilong/requestdemo.jsp
*
*
* request.getRequestURL()
* http://localhost:8080/feilong/requestdemo.jsp
*
*
*
*
* @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();
}
/**
* 获得请求的全地址.
*
*
* - 如果request不含queryString,直接返回 requestURL(比如post请求)
* - 如果request含queryString,直接返回 requestURL+编码后的queryString
*
*
* @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} 的方式去除乱码.
*
*
* {@link CharsetType#ISO_8859_1} 是JAVA网络传输使用的标准 字符集
*
*
* 关于URI Encoding
*
*
*
* - tomcat server.xml Connector URIEncoding="UTF-8"
* - {@code
}
*
*
*
* @param str
* 字符串
* @param charsetType
* 字符编码,建议使用 {@link CharsetType} 定义好的常量
* @return 如果 str
是null或者empty,返回 {@link StringUtils#EMPTY}
* @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.
*
*
* 区分 http 和https.
*
*
* @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程序在调用该方法转发之前可以对请求进行前期预处理.
*
*
* 该方法将 checked exception 转成了 unchecked exception,方便书写和调用
*
*
*
* Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server.
* This method allows one servlet to do preliminary processing of a request and another resource to generate the response.
*
*
*
* For a RequestDispatcher
obtained via getRequestDispatcher()
, the ServletRequest
object has its
* path elements and parameters adjusted to match the path of the target resource.
*
*
*
* forward
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 IllegalStateException
. Uncommitted output in
* the response buffer is automatically cleared before the forward.
*
*
*
* 如果 path
是null,抛出 {@link NullPointerException}
*
*
* @param path
* a String specifying the pathname to the resource.
* If it is relative, it must be relative against the current servlet.The
* pathname specified may be relative, although it cannot extend outside the current servlet context.
* If the path begins with a "/" it is interpreted as relative to the current context root.
* This method returns null if the servlet container cannot return a RequestDispatcher.
* 如果 path
是null,抛出 {@link NullPointerException}
* @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){
//since 2.0.1
Validate.notNull(path, "path can't be null!");
LOGGER.debug("will forward to path:[{}]", path);
try{
RequestDispatcher requestDispatcher = request.getRequestDispatcher(path);
requestDispatcher.forward(request, response);
}catch (ServletException | IOException e){
throw new RequestException("when forward to:" + path, e);
}
}
/**
* 用于将 {@link RequestDispatcher} 对象封装的资源内容作为当前响应内容的一部分包含进来,从而实现可编程服务器的服务器端包含功能.
*
*
* 该方法将 checked exception 转成了 unchecked exception,方便书写和调用
*
*
*
* Includes the content of a resource (servlet, JSP page,HTML file) in the response.
* In essence, this method enables programmatic server-side includes.
*
*
*
* 注:被包含的Servlet程序不能改变响应信息的状态码和响应头,如果里面包含这样的语句将被忽略.
* The {@link ServletResponse} object has its path elements and parameters remain unchanged from the caller's.
* The included servlet cannot change the response status code or set headers; any attempt to make a change is ignored.
*
*
* @param path
* a String specifying the pathname to the resource.
* If it is relative, it must be relative against the current servlet.The
* pathname specified may be relative, although it cannot extend outside the current servlet context.
* If the path begins with a "/" it is interpreted as relative to the current context root.
* This method returns null if the servlet container cannot return a RequestDispatcher.
* 如果 path
是null,抛出 {@link NullPointerException}
* @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){
//since 2.0.1
Validate.notNull(path, "path can't be null!");
LOGGER.debug("will include to path:[{}]", path);
try{
RequestDispatcher requestDispatcher = request.getRequestDispatcher(path);
requestDispatcher.include(request, response);
}catch (ServletException | IOException e){
throw new RequestException("when include:" + path, e);
}
}
//--------------------------Header-------------------------------------
/**
* 获得客户端真实ip地址.
*
*
* 这样做的好处是,对开发透明
*
*
* @param request
* the request
* @return 获得客户端ip地址
* @see "org.apache.catalina.valves.RemoteIpValve"
* @see Story behind
* X-Forwarded-For and X-Real-IP headers
* @see nginx做负载CDN加速获取端真实ip
*/
public static String getClientIp(HttpServletRequest request){
Map map = newLinkedHashMap();
for (String ipHeaderName : IP_HEADER_NAMES){
//The header name is case insensitive (不区分大小写)
map.put(ipHeaderName, request.getHeader(ipHeaderName));
}
map.put("request.getRemoteAddr()", request.getRemoteAddr());
return getClientIp(map);
}
/**
* Gets the client ip.
*
* @param map
* the map
* @return the client ip
* @since 3.0.0
*/
private static String getClientIp(Map map){
if (LOGGER.isTraceEnabled()){
LOGGER.trace("ips:{}", JsonUtil.format(map));
}
//---------------------------------------------------------------
for (Map.Entry entry : map.entrySet()){
String value = entry.getValue();
//IPV4 127.0.0.1
//IPV6 2610:00f8:0c34:67f9:0200:83ff:fe94:4c36
if (isNullOrEmpty(value) || "unknown".equalsIgnoreCase(value)){
continue;
}
//取非空值里面第一个 ,比如 X-Forwarded-For: client1, proxy1, proxy2.
//已去空格 忽略empty元素
String[] ips = tokenizeToStringArray(value, ",");
return ips[0];
}
return EMPTY;
}
//---------------------------------------------------------------
/**
* User Agent中文名为用户代理,简称 UA.
*
*
* 它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等.
*
*
* @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.
*
*
* referer是浏览器在用户提交请求当前页面中的一个链接时,将当前页面的URL放在头域中提交给服务端的,如当前页面为a.html,
* 它里面有一个b.html的链接,当用户要访问b.html时浏览器就会把a.html作为referer发给服务端.
*
*
* 注意:
*
* 请用于常规请求,必须走http/https协议才有值,javascript跳转无效
*
* 也就是说要通过<a href="url">sss</a>标记才能获得那个值
* 而通过改变location或是<a href="javascript:location='url'">sss</a>都是得不到那个值得
*
*
* 不能正常取值的情况:
*
*
* - 从收藏夹链接
* - 用Window.open打开地址或者自定义的地址
* - 利用Jscript的location.href or location.replace()
* - 在浏览器直接输入地址
* - Response.Redirect
* - Response.AddHeader或{@code }转向
*
*
*
* 关于referer的获取问题
*
*
* 在上一页面做跳转操作,可以在下一页面获得上一页面的Referer从而判断页面的来路。
*
*
* 目前web开发有以下几种页面跳转方式:
*
*
*
*
*
*
* 方式
* 是否支持跨域
* 是否可以取到referer
* 代码示例
*
*
*
* {@link RequestDispatcher} 跳转
* 不支持跨域
* 目的页面无法取得referer
*
*
*
* RequestDispatcher rd = request.getRequestDispatcher(url);
* rd.forward(request, response);
*
*
*
*
*
*
* response.setHeader
* 支持跨域
* 目的页面无法取得referer
*
*
*
* response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
* response.setHeader("Location", url);
*
*
*
*
*
*
*
* response.sendRedirect(url)
* 支持跨域
* 目的页面无法取得referer
*
*
*
*
* 用form表单, post方法提交
* 既可跨域
* 又能得到referer
* 并且支持form表单的action属性中url使用参数
*
*
*
* 用form表单, get方法提交
* 既可跨域
* 又能得到referer
*
*
* 但不支持form表单的action属性中url使用参数,
* 这种方式不会将action的值后面添加"?"提交到web服务器。
* 如果actio中的url就含有"?"则会将"?"后的数据忽略掉。而post方式不存在这个问题
*
*
*
*
*
* 使用html中href来跳转页面
*
* 目的页面可以获得referer
*
*
*
*
*
*
*
*
* @param request
* the request
* @return 如果request没有指定名称 {@link HttpHeaders#REFERER} 的header,那么返回null
* @see HttpHeaders#REFERER
* @see 关于referer的获取问题
*/
public static String getHeaderReferer(HttpServletRequest request){
return request.getHeader(REFERER);
}
/**
* 1、Origin字段里只包含是谁发起的请求,并没有其他信息 (通常情况下是方案,主机和活动文档URL的端口).
* 跟Referer不一样的是,Origin字段并没有包含涉及到用户隐私的URL路径和请求内容,这个尤其重要.
* 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);
}
//---------------------------------------------------------------
/**
* 判断一个请求是否是微信浏览器的请求.
*
* 说明:
*
*
*
* - 在iPhone下,返回
* Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B176 MicroMessenger/4.3.2
*
* - 在Android下,返回
* Mozilla/5.0 (Linux; U; Android 2.3.6; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile
* Safari/533.1 MicroMessenger/4.5.255
*
*
* 不难发现微信浏览器为 MicroMessenger,并且有版本号,也可以判断手机类型为iPhone还是Android
*
*
* @param request
* the request
* @return 如果没有ua,那么返回false;否则判断ua 里面是否包含 micromessenger 值
* @see 微信浏览器的HTTP_USER_AGENT
* @see 如何在服务器端判断请求的客户端是微信调用的浏览器?
* @since 1.10.4
*/
public static boolean isWechatRequest(HttpServletRequest request){
String userAgent = getHeaderUserAgent(request);
if (isNullOrEmpty(userAgent)){
return false;
}
return (userAgent.toLowerCase()).contains("micromessenger");
}
/**
* 判断一个请求是否是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);
}
//---------------------------------------------------------------
/**
* 遍历显示request的attribute,将 name /attributeValue 存入到map(TreeMap).
*
*
* 注意:
*
*
*
* 1.目前如果属性有级联关系,如果直接转json,可能会报错,此时建议使用 {@link JsonUtil#formatSimpleMap(Map, Class...) }
*
*
*
* 2.可以做返回的map进行remove操作,不会影响request的 Attribute
*
*
*
* @param request
* the request
* @return 如果{@link javax.servlet.ServletRequest#getAttributeNames()} 是null或者empty,返回{@link Collections#emptyMap()}
*/
public static Map getAttributeMap(HttpServletRequest request){
Enumeration attributeNames = request.getAttributeNames();
if (isNullOrEmpty(attributeNames)){
return emptyMap();
}
//---------------------------------------------------------------
Map 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);
}
/**
* 获取request body中的内容.
*
* 使用场景:
*
*
* wechat notify 的时候数据在 request body 里面,需要提取
*
*
* 说明:
*
*
* - request.getInputStream(); request.getReader();和request.getParameter("key");
* 这三个函数中任何一个函数执行一次后(可正常读取body数据),之后再执行就无效了。
*
*
*
* @param request
* the request
* @return the request body
* @see "org.springframework.web.bind.annotation.RequestBody"
* @see "org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor"
* @see Get the POST request
* body from HttpServletRequest
*
* @since 1.10.6
* @since 1.14.2 add 多次获取特性
*/
public static String getRequestBody(HttpServletRequest request){
//since 1.14.2
String requestBodyInRequestScope = getAttribute(request, REQUEST_BODY_SCOPE_ATTRIBUTE_NAME);
if (null != requestBodyInRequestScope){
//有就返回
return requestBodyInRequestScope;
}
//---------------------------------------------------------------
//解析
String requestBody = parseBody(request);
if (null != requestBody){
request.setAttribute(REQUEST_BODY_SCOPE_ATTRIBUTE_NAME, requestBody);
}
return requestBody;
}
/**
* Parses the body.
*
* @param request
* the request
* @return the string
* @throws UncheckedIOException
* the unchecked IO exception
* @since 1.14.2
*/
private static String parseBody(HttpServletRequest request){
try{
//Retrieves the body of the request as character data using a BufferedReader.
//The reader translates the character data according to the character encoding used on the body.
//Either this method or getInputStream may be called to read the body, not both.
BufferedReader reader = request.getReader();
return ReaderUtil.toString(reader);
}catch (IOException e){
throw new UncheckedIOException(e);
}
}
}