com.xiaoleilu.hutool.http.HttpUtil Maven / Gradle / Ivy
package com.xiaoleilu.hutool.http;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import com.xiaoleilu.hutool.collection.CollectionUtil;
import com.xiaoleilu.hutool.convert.Convert;
import com.xiaoleilu.hutool.io.FastByteArrayOutputStream;
import com.xiaoleilu.hutool.io.FileUtil;
import com.xiaoleilu.hutool.io.IORuntimeException;
import com.xiaoleilu.hutool.io.IoUtil;
import com.xiaoleilu.hutool.io.StreamProgress;
import com.xiaoleilu.hutool.util.ArrayUtil;
import com.xiaoleilu.hutool.util.CharsetUtil;
import com.xiaoleilu.hutool.util.ReUtil;
import com.xiaoleilu.hutool.util.StrUtil;
/**
* Http请求工具类
*
* @author xiaoleilu
*/
public class HttpUtil {
/** 正则:匹配meta标签的编码信息 */
public static final Pattern CHARSET_PATTERN = Pattern.compile("
* 默认检测的Header:
* 1、X-Forwarded-For
* 2、X-Real-IP
* 3、Proxy-Client-IP
* 4、WL-Proxy-Client-IP
* otherHeaderNames参数用于自定义检测的Header
*
* @param request 请求对象
* @param otherHeaderNames 其他自定义头文件
* @return IP地址
*/
public static String getClientIP(javax.servlet.http.HttpServletRequest request, String... otherHeaderNames) {
String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" };
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
headers = ArrayUtil.addAll(headers, otherHeaderNames);
}
String ip;
for (String header : headers) {
ip = request.getHeader(header);
if (false == isUnknow(ip)) {
return getMultistageReverseProxyIp(ip);
}
}
ip = request.getRemoteAddr();
return getMultistageReverseProxyIp(ip);
}
/**
* 检测是否https
*
* @param url URL
* @return 是否https
*/
public static boolean isHttps(String url) {
return url.toLowerCase().startsWith("https");
}
/**
* 创建Http请求对象
*
* @param method 方法枚举{@link Method}
* @param url 请求的URL,可以使HTTP或者HTTPS
* @return {@link HttpRequest}
* @since 3.0.9
*/
public static HttpRequest createRequest(Method method, String url) {
return new HttpRequest(url).method(method);
}
/**
* 创建Http GET请求对象
*
* @param url 请求的URL,可以使HTTP或者HTTPS
* @return {@link HttpRequest}
* @since 3.2.0
*/
public static HttpRequest createGet(String url) {
return HttpRequest.get(url);
}
/**
* 创建Http POST请求对象
*
* @param url 请求的URL,可以使HTTP或者HTTPS
* @return {@link HttpRequest}
* @since 3.2.0
*/
public static HttpRequest createPost(String url) {
return HttpRequest.post(url);
}
/**
* 发送get请求
*
* @param urlString 网址
* @param customCharset 自定义请求字符集,如果字符集获取不到,使用此字符集
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
*/
public static String get(String urlString, String customCharset) {
return HttpRequest.get(urlString).charset(customCharset).execute().body();
}
/**
* 发送get请求
*
* @param urlString 网址
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
*/
public static String get(String urlString) {
return get(urlString, HttpRequest.TIMEOUT_DEFAULT);
}
/**
* 发送get请求
*
* @param urlString 网址
* @param timeout 超时时长,-1表示默认超时,单位毫秒
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
* @since 3.2.0
*/
public static String get(String urlString, int timeout) {
return HttpRequest.get(urlString).timeout(timeout).execute().body();
}
/**
* 发送get请求
*
* @param urlString 网址
* @param paramMap post表单数据
* @return 返回数据
*/
public static String get(String urlString, Map paramMap) {
return HttpRequest.get(urlString).form(paramMap).execute().body();
}
/**
* 发送get请求
*
* @param urlString 网址
* @param paramMap post表单数据
* @param timeout 超时时长,-1表示默认超时,单位毫秒
* @return 返回数据
* @since 3.3.0
*/
public static String get(String urlString, Map paramMap, int timeout) {
return HttpRequest.get(urlString).form(paramMap).timeout(timeout).execute().body();
}
/**
* 发送post请求
*
* @param urlString 网址
* @param paramMap post表单数据
* @return 返回数据
*/
public static String post(String urlString, Map paramMap) {
return post(urlString, paramMap, HttpRequest.TIMEOUT_DEFAULT);
}
/**
* 发送post请求
*
* @param urlString 网址
* @param paramMap post表单数据
* @param timeout 超时时长,-1表示默认超时,单位毫秒
* @return 返回数据
* @since 3.2.0
*/
public static String post(String urlString, Map paramMap, int timeout) {
return HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().body();
}
/**
* 发送post请求
* 请求体body参数支持两种类型:
*
*
* 1. 标准参数,例如 a=1&b=2 这种格式
* 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
*
*
* @param urlString 网址
* @param body post表单数据
* @return 返回数据
*/
public static String post(String urlString, String body) {
return post(urlString, body, HttpRequest.TIMEOUT_DEFAULT);
}
/**
* 发送post请求
* 请求体body参数支持两种类型:
*
*
* 1. 标准参数,例如 a=1&b=2 这种格式
* 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
*
*
* @param urlString 网址
* @param body post表单数据
* @param timeout 超时时长,-1表示默认超时,单位毫秒
* @return 返回数据
* @since 3.2.0
*/
public static String post(String urlString, String body, int timeout) {
return HttpRequest.post(urlString).timeout(timeout).body(body).execute().body();
}
// ---------------------------------------------------------------------------------------- download
/**
* 下载远程文本
*
* @param url 请求的url
* @param customCharsetName 自定义的字符集
* @return 文本
*/
public static String downloadString(String url, String customCharsetName) {
return downloadString(url, CharsetUtil.charset(customCharsetName), null);
}
/**
* 下载远程文本
*
* @param url 请求的url
* @param customCharset 自定义的字符集,可以使用{@link CharsetUtil#charset} 方法转换
* @return 文本
*/
public static String downloadString(String url, Charset customCharset) {
return downloadString(url, customCharset, null);
}
/**
* 下载远程文本
*
* @param url 请求的url
* @param customCharset 自定义的字符集,可以使用{@link CharsetUtil#charset} 方法转换
* @param streamPress 进度条 {@link StreamProgress}
* @return 文本
*/
public static String downloadString(String url, Charset customCharset, StreamProgress streamPress) {
if (StrUtil.isBlank(url)) {
throw new NullPointerException("[url] is null!");
}
FastByteArrayOutputStream out = new FastByteArrayOutputStream();
download(url, out, true, streamPress);
return null == customCharset ? out.toString() : out.toString(customCharset);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param dest 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
* @return 文件大小
*/
public static long downloadFile(String url, String dest) {
return downloadFile(url, FileUtil.file(dest));
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
* @return 文件大小
*/
public static long downloadFile(String url, File destFile) {
return downloadFile(url, destFile, null);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
* @param streamProgress 进度条
* @return 文件大小
*/
public static long downloadFile(String url, File destFile, StreamProgress streamProgress) {
if (StrUtil.isBlank(url)) {
throw new NullPointerException("[url] is null!");
}
if (null == destFile) {
throw new NullPointerException("[destFile] is null!");
}
if (destFile.isDirectory()) {
String fileName = StrUtil.subSuf(url, url.lastIndexOf('/') + 1);
if (StrUtil.isBlank(fileName)) {
fileName = HttpUtil.encode(url, CharsetUtil.CHARSET_UTF_8);
}
destFile = FileUtil.file(destFile, fileName);
}
OutputStream out = null;
try {
out = FileUtil.getOutputStream(destFile);
return download(url, out, true, streamProgress);
} catch (IORuntimeException e) {
throw new HttpException(e);
}
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param out 将下载内容写到输出流中 {@link OutputStream}
* @param isCloseOut 是否关闭输出流
* @return 文件大小
*/
public static long download(String url, OutputStream out, boolean isCloseOut) {
return download(url, out, isCloseOut, null);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param out 将下载内容写到输出流中 {@link OutputStream}
* @param isCloseOut 是否关闭输出流
* @param streamProgress 进度条
* @return 文件大小
*/
public static long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress) {
if (StrUtil.isBlank(url)) {
throw new NullPointerException("[url] is null!");
}
if (null == out) {
throw new NullPointerException("[out] is null!");
}
return HttpRequest.get(url).executeAsync().writeBody(out, isCloseOut, streamProgress);
}
/**
* 将Map形式的Form表单数据转换为Url参数形式,不做编码
*
* @param paramMap 表单数据
* @return url参数
*/
public static String toParams(Map paramMap) {
return toParams(paramMap, CharsetUtil.CHARSET_UTF_8);
}
/**
* 将Map形式的Form表单数据转换为Url参数形式
* 编码键和值对
*
* @param paramMap 表单数据
* @param charsetName 编码
* @return url参数
*/
public static String toParams(Map paramMap, String charsetName) {
return toParams(paramMap, CharsetUtil.charset(charsetName));
}
/**
* 将Map形式的Form表单数据转换为Url参数形式
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
*
*
* key1=v1&key2=&key3=v3
*
*
* @param paramMap 表单数据
* @param charset 编码
* @return url参数
*/
public static String toParams(Map paramMap, Charset charset) {
if (CollectionUtil.isEmpty(paramMap)) {
return StrUtil.EMPTY;
}
if (null == charset) {// 默认编码为系统编码
charset = CharsetUtil.CHARSET_UTF_8;
}
final StringBuilder sb = new StringBuilder();
boolean isFirst = true;
String key;
Object value;
String valueStr;
for (Entry item : paramMap.entrySet()) {
if (isFirst) {
isFirst = false;
} else {
sb.append("&");
}
key = item.getKey();
value = item.getValue();
if (value instanceof Iterable) {
value = CollectionUtil.join((Iterable>) value, ",");
} else if (value instanceof Iterator) {
value = CollectionUtil.join((Iterator>) value, ",");
}
valueStr = Convert.toStr(value);
if (StrUtil.isNotEmpty(key)) {
sb.append(encode(key, charset)).append("=");
if (StrUtil.isNotEmpty(valueStr)) {
sb.append(encode(valueStr, charset));
}
}
}
return sb.toString();
}
/**
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
*
* @param paramsStr 参数字符串(或者带参数的Path)
* @param charset 字符集
* @return 参数Map
*/
public static Map> decodeParams(String paramsStr, String charset) {
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
}
// 去掉Path部分
int pathEndPos = paramsStr.indexOf('?');
if (pathEndPos > 0) {
paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1);
}
final Map> params = new LinkedHashMap>();
final int len = paramsStr.length();
String name = null;
int pos = 0; // 未处理字符开始位置
int i; // 未处理字符结束位置
char c; // 当前字符
for (i = 0; i < len; i++) {
c = paramsStr.charAt(i);
if (c == '=' && name == null) { // 键值对的分界点
if (pos != i) {
name = paramsStr.substring(pos, i);
}
pos = i + 1;
} else if (c == '&' || c == ';') { // 参数对的分界点
if (name == null && pos != i) {
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else if (name != null) {
addParam(params, name, paramsStr.substring(pos, i), charset);
name = null;
}
pos = i + 1;
}
}
// 处理结尾
if (pos != i) {
if (name == null) {
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else {
addParam(params, name, paramsStr.substring(pos, i), charset);
}
} else if (name != null) {
addParam(params, name, StrUtil.EMPTY, charset);
}
return params;
}
/**
* 将表单数据加到URL中(用于GET表单提交)
*
* @param url URL
* @param form 表单数据
* @return 合成后的URL
*/
public static String urlWithForm(String url, Map form) {
final String queryString = toParams(form, CharsetUtil.UTF_8);
return urlWithForm(url, queryString);
}
/**
* 将表单数据字符串加到URL中(用于GET表单提交)
*
* @param url URL
* @param queryString 表单数据字符串
* @return 拼接后的字符串
*/
public static String urlWithForm(String url, String queryString) {
if (StrUtil.isNotBlank(queryString)) {
if (url.contains("?")) {
// 原URL已经带参数
url += "&" + queryString;
}
url += url.endsWith("?") ? queryString : "?" + queryString;
}
return url;
}
/**
* 从Http连接的头信息中获得字符集
* 从ContentType中获取
*
* @param conn HTTP连接对象
* @return 字符集
*/
public static String getCharset(HttpURLConnection conn) {
if (conn == null) {
return null;
}
String charset = ReUtil.get(CHARSET_PATTERN, conn.getContentType(), 1);
return charset;
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (false == isUnknow(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknow(String checkString) {
return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
/**
* 从流中读取内容
* 首先尝试使用charset编码读取内容(如果为空默认UTF-8),如果isGetCharsetFromContent为true,则通过正则在正文中获取编码信息,转换为指定编码;
*
* @param in 输入流
* @param charset 字符集
* @param isGetCharsetFromContent 是否从返回内容中获得编码信息
* @return 内容
* @throws IOException IO异常
*/
public static String getString(InputStream in, Charset charset, boolean isGetCharsetFromContent) throws IOException {
final byte[] contentBytes = IoUtil.readBytes(in);
return getString(contentBytes, charset, isGetCharsetFromContent);
}
/**
* 从流中读取内容
* 首先尝试使用charset编码读取内容(如果为空默认UTF-8),如果isGetCharsetFromContent为true,则通过正则在正文中获取编码信息,转换为指定编码;
*
* @param contentBytes 内容byte数组
* @param charset 字符集
* @param isGetCharsetFromContent 是否从返回内容中获得编码信息
* @return 内容
* @throws IOException IO异常
*/
public static String getString(byte[] contentBytes, Charset charset, boolean isGetCharsetFromContent) throws IOException {
if (null == contentBytes) {
return null;
}
if (null == charset) {
charset = CharsetUtil.CHARSET_UTF_8;
}
String content = new String(contentBytes, charset);
if (isGetCharsetFromContent) {
final String charsetInContentStr = ReUtil.get(CHARSET_PATTERN, content, 1);
if (StrUtil.isNotBlank(charsetInContentStr)) {
Charset charsetInContent = null;
try {
charsetInContent = Charset.forName(charsetInContentStr);
} catch (Exception e) {
if (StrUtil.containsIgnoreCase(charsetInContentStr, "utf-8")) {
charsetInContent = CharsetUtil.CHARSET_UTF_8;
} else if (StrUtil.containsIgnoreCase(charsetInContentStr, "gbk")) {
charsetInContent = CharsetUtil.CHARSET_GBK;
}
// ignore
}
if (null != charsetInContent && false == charset.equals(charsetInContent)) {
content = new String(contentBytes, charsetInContent);
}
}
}
return content;
}
/**
* 根据文件扩展名获得MimeType
*
* @param filePath 文件路径或文件名
* @return MimeType
*/
public static String getMimeType(String filePath) {
return URLConnection.getFileNameMap().getContentTypeFor(filePath);
}
/**
* 从请求参数的body中判断请求的Content-Type类型,支持的类型有:
*
*
* 1. application/json
* 1. application/xml
*
*
* @param body 请求参数体
* @return Content-Type类型,如果无法判断返回null
* @since 3.2.0
*/
public static String getContentTypeByRequestBody(String body) {
String contentType = null;
if (StrUtil.isNotBlank(body)) {
char firstChar = body.charAt(0);
switch (firstChar) {
case '{':
case '[':
// JSON请求体
contentType = "application/json";
break;
case '<':
// XML请求体
contentType = "application/xml";
break;
default:
break;
}
}
return contentType;
}
// ----------------------------------------------------------------------------------------- Private method start
/**
* 将键值对加入到值为List类型的Map中
*
* @param params 参数
* @param name key
* @param value value
* @param charset 编码
*/
private static void addParam(Map> params, String name, String value, String charset) {
List values = params.get(name);
if (values == null) {
values = new ArrayList(1); // 一般是一个参数
params.put(name, values);
}
values.add(decode(value, charset));
}
// ----------------------------------------------------------------------------------------- Private method start end
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy