add http engine

This commit is contained in:
Looly
2022-10-26 00:11:19 +08:00
parent 1ab5fd5dcb
commit c1fe82d2ce
75 changed files with 1878 additions and 410 deletions

View File

@@ -22,6 +22,14 @@
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<!-- 第三方HTTP客户端库 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
@@ -29,11 +37,18 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
@@ -46,5 +61,12 @@
<version>0.1.2</version>
<scope>test</scope>
</dependency>
<!-- 仅用于测试 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -3,6 +3,8 @@ package cn.hutool.http;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.meta.Header;
import java.util.ArrayList;
import java.util.Collections;

View File

@@ -1,5 +1,9 @@
package cn.hutool.http;
import cn.hutool.http.client.engine.jdk.HttpInterceptor;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
/**
* 全局的拦截器<br>
* 包括请求拦截器和响应拦截器
@@ -69,7 +73,7 @@ public enum GlobalInterceptor {
/**
* 复制请求过滤器列表
*
* @return {@link cn.hutool.http.HttpInterceptor.Chain}
* @return {@link HttpInterceptor.Chain}
*/
HttpInterceptor.Chain<HttpRequest> getCopiedRequestInterceptor() {
final HttpInterceptor.Chain<HttpRequest> copied = new HttpInterceptor.Chain<>();
@@ -82,7 +86,7 @@ public enum GlobalInterceptor {
/**
* 复制响应过滤器列表
*
* @return {@link cn.hutool.http.HttpInterceptor.Chain}
* @return {@link HttpInterceptor.Chain}
*/
HttpInterceptor.Chain<HttpResponse> getCopiedResponseInterceptor() {
final HttpInterceptor.Chain<HttpResponse> copied = new HttpInterceptor.Chain<>();

View File

@@ -2,6 +2,9 @@ package cn.hutool.http;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.ssl.SSLUtil;
import cn.hutool.http.client.engine.jdk.HttpInterceptor;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
@@ -28,40 +31,40 @@ public class HttpConfig {
/**
* 默认连接超时
*/
int connectionTimeout = HttpGlobalConfig.getTimeout();
public int connectionTimeout = HttpGlobalConfig.getTimeout();
/**
* 默认读取超时
*/
int readTimeout = HttpGlobalConfig.getTimeout();
public int readTimeout = HttpGlobalConfig.getTimeout();
/**
* 是否禁用缓存
*/
boolean isDisableCache;
public boolean isDisableCache;
/**
* 最大重定向次数
*/
int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount();
public int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount();
/**
* 代理
*/
Proxy proxy;
public Proxy proxy;
/**
* HostnameVerifier用于HTTPS安全连接
*/
HostnameVerifier hostnameVerifier;
public HostnameVerifier hostnameVerifier;
/**
* SSLSocketFactory用于HTTPS安全连接
*/
SSLSocketFactory ssf;
public SSLSocketFactory ssf;
/**
* Chuncked块大小0或小于0表示不设置Chuncked模式
*/
int blockSize;
public int blockSize;
/**
* 获取是否忽略响应读取时可能的EOF异常。<br>
@@ -79,16 +82,16 @@ public class HttpConfig {
/**
* 请求前的拦截器,用于在请求前重新编辑请求
*/
final HttpInterceptor.Chain<HttpRequest> requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor();
public final HttpInterceptor.Chain<HttpRequest> requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor();
/**
* 响应后的拦截器,用于在响应后处理逻辑
*/
final HttpInterceptor.Chain<HttpResponse> responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor();
public final HttpInterceptor.Chain<HttpResponse> responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor();
/**
* 重定向时是否使用拦截器
*/
boolean interceptorOnRedirect;
public boolean interceptorOnRedirect;
/**
* 设置超时,单位:毫秒<br>
@@ -297,4 +300,13 @@ public class HttpConfig {
this.interceptorOnRedirect = interceptorOnRedirect;
return this;
}
/**
* 获取是否忽略响应读取时可能的EOF异常。
*
* @return 是否忽略响应读取时可能的EOF异常。
*/
public boolean isIgnoreEOFError() {
return ignoreEOFError;
}
}

View File

@@ -3,7 +3,7 @@ package cn.hutool.http;
import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.client.cookie.GlobalCookieManager;
import java.io.Serializable;
import java.lang.reflect.Field;

View File

@@ -1,56 +0,0 @@
package cn.hutool.http;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
/**
* HTTP资源可自定义Content-Type
*
* @author looly
* @since 5.7.17
*/
public class HttpResource implements Resource, Serializable {
private static final long serialVersionUID = 1L;
private final Resource resource;
private final String contentType;
/**
* 构造
*
* @param resource 资源,非空
* @param contentType Content-Type类型{@code null}表示不设置
*/
public HttpResource(final Resource resource, final String contentType) {
this.resource = Assert.notNull(resource, "Resource must be not null !");
this.contentType = contentType;
}
@Override
public String getName() {
return resource.getName();
}
@Override
public URL getUrl() {
return resource.getUrl();
}
@Override
public InputStream getStream() {
return resource.getStream();
}
/**
* 获取自定义Content-Type类型
*
* @return Content-Type类型
*/
public String getContentType() {
return this.contentType;
}
}

View File

@@ -1,222 +0,0 @@
package cn.hutool.http;
/**
* HTTP状态码
*
* @author Looly
* @see java.net.HttpURLConnection
*
*/
public class HttpStatus {
/* 2XX: generally "OK" */
/**
* HTTP Status-Code 200: OK.
*/
public static final int HTTP_OK = 200;
/**
* HTTP Status-Code 201: Created.
*/
public static final int HTTP_CREATED = 201;
/**
* HTTP Status-Code 202: Accepted.
*/
public static final int HTTP_ACCEPTED = 202;
/**
* HTTP Status-Code 203: Non-Authoritative Information.
*/
public static final int HTTP_NOT_AUTHORITATIVE = 203;
/**
* HTTP Status-Code 204: No Content.
*/
public static final int HTTP_NO_CONTENT = 204;
/**
* HTTP Status-Code 205: Reset Content.
*/
public static final int HTTP_RESET = 205;
/**
* HTTP Status-Code 206: Partial Content.
*/
public static final int HTTP_PARTIAL = 206;
/* 3XX: relocation/redirect */
/**
* HTTP Status-Code 300: Multiple Choices.
*/
public static final int HTTP_MULT_CHOICE = 300;
/**
* HTTP Status-Code 301: Moved Permanently.
*/
public static final int HTTP_MOVED_PERM = 301;
/**
* HTTP Status-Code 302: Temporary Redirect.
*/
public static final int HTTP_MOVED_TEMP = 302;
/**
* HTTP Status-Code 303: See Other.
*/
public static final int HTTP_SEE_OTHER = 303;
/**
* HTTP Status-Code 304: Not Modified.
*/
public static final int HTTP_NOT_MODIFIED = 304;
/**
* HTTP Status-Code 305: Use Proxy.
*/
public static final int HTTP_USE_PROXY = 305;
/**
* HTTP 1.1 Status-Code 307: Temporary Redirect.<br>
* 见RFC-7231
*/
public static final int HTTP_TEMP_REDIRECT = 307;
/**
* HTTP 1.1 Status-Code 308: Permanent Redirect 永久重定向<br>
* 见RFC-7231
*/
public static final int HTTP_PERMANENT_REDIRECT = 308;
/* 4XX: client error */
/**
* HTTP Status-Code 400: Bad Request.
*/
public static final int HTTP_BAD_REQUEST = 400;
/**
* HTTP Status-Code 401: Unauthorized.
*/
public static final int HTTP_UNAUTHORIZED = 401;
/**
* HTTP Status-Code 402: Payment Required.
*/
public static final int HTTP_PAYMENT_REQUIRED = 402;
/**
* HTTP Status-Code 403: Forbidden.
*/
public static final int HTTP_FORBIDDEN = 403;
/**
* HTTP Status-Code 404: Not Found.
*/
public static final int HTTP_NOT_FOUND = 404;
/**
* HTTP Status-Code 405: Method Not Allowed.
*/
public static final int HTTP_BAD_METHOD = 405;
/**
* HTTP Status-Code 406: Not Acceptable.
*/
public static final int HTTP_NOT_ACCEPTABLE = 406;
/**
* HTTP Status-Code 407: Proxy Authentication Required.
*/
public static final int HTTP_PROXY_AUTH = 407;
/**
* HTTP Status-Code 408: Request Time-Out.
*/
public static final int HTTP_CLIENT_TIMEOUT = 408;
/**
* HTTP Status-Code 409: Conflict.
*/
public static final int HTTP_CONFLICT = 409;
/**
* HTTP Status-Code 410: Gone.
*/
public static final int HTTP_GONE = 410;
/**
* HTTP Status-Code 411: Length Required.
*/
public static final int HTTP_LENGTH_REQUIRED = 411;
/**
* HTTP Status-Code 412: Precondition Failed.
*/
public static final int HTTP_PRECON_FAILED = 412;
/**
* HTTP Status-Code 413: Request Entity Too Large.
*/
public static final int HTTP_ENTITY_TOO_LARGE = 413;
/**
* HTTP Status-Code 414: Request-URI Too Large.
*/
public static final int HTTP_REQ_TOO_LONG = 414;
/**
* HTTP Status-Code 415: Unsupported Media Type.
*/
public static final int HTTP_UNSUPPORTED_TYPE = 415;
/* 5XX: server error */
/**
* HTTP Status-Code 500: Internal Server Error.
*/
public static final int HTTP_INTERNAL_ERROR = 500;
/**
* HTTP Status-Code 501: Not Implemented.
*/
public static final int HTTP_NOT_IMPLEMENTED = 501;
/**
* HTTP Status-Code 502: Bad Gateway.
*/
public static final int HTTP_BAD_GATEWAY = 502;
/**
* HTTP Status-Code 503: Service Unavailable.
*/
public static final int HTTP_UNAVAILABLE = 503;
/**
* HTTP Status-Code 504: Gateway Timeout.
*/
public static final int HTTP_GATEWAY_TIMEOUT = 504;
/**
* HTTP Status-Code 505: HTTP Version Not Supported.
*/
public static final int HTTP_VERSION = 505;
/**
* 是否为重定向状态码
* @param responseCode 被检查的状态码
* @return 是否为重定向状态码
* @since 5.6.3
*/
public static boolean isRedirected(final int responseCode){
return responseCode == HTTP_MOVED_PERM
|| responseCode == HTTP_MOVED_TEMP
|| responseCode == HTTP_SEE_OTHER
// issue#1504@Github307和308是RFC 7538中http 1.1定义的规范
|| responseCode == HTTP_TEMP_REDIRECT
|| responseCode == HTTP_PERMANENT_REDIRECT;
}
}

View File

@@ -13,7 +13,11 @@ import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.client.HttpDownloader;
import cn.hutool.http.client.cookie.GlobalCookieManager;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.meta.Method;
import cn.hutool.http.server.SimpleServer;
import java.io.File;

View File

@@ -0,0 +1,27 @@
package cn.hutool.http.client;
import java.io.Closeable;
/**
* HTTP客户端引擎接口通过不同实现完成HTTP请求发送
*
* @author looly
* @since 6.0.0
*/
public interface ClientEngine extends Closeable {
/**
* 发送HTTP请求
* @param message HTTP请求消息
* @return 响应内容
*/
Response send(Request message);
/**
* 获取原始引擎的钩子方法,用于自定义特殊属性,如插件等
*
* @return 对应HTTP客户端实现的引擎对象
* @since 6.0.0
*/
Object getRawEngine();
}

View File

@@ -0,0 +1,227 @@
package cn.hutool.http.client;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.http.meta.Header;
import java.net.HttpCookie;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* HTTP请求头的存储和相关方法
*
* @param <T> 返回对象类型,方便链式编程
*/
@SuppressWarnings("unchecked")
public interface Headers<T extends Headers<T>> {
// region ----------------------------------------------------------- headers
/**
* 获取headers
*
* @return Headers Map
*/
Map<String, List<String>> headers();
/**
* 设置一个header<br>
* 如果覆盖模式,则替换之前的值,否则加入到值列表中<br>
* 如果给定值为{@code null},则删除这个头信息
*
* @param name Header名{@code null}跳过
* @param value Header值{@code null}表示删除name对应的头
* @param isOverride 是否覆盖已有值
* @return this
*/
T header(final String name, final String value, final boolean isOverride);
/**
* 获取指定的Header值如果不存在返回{@code null}
*
* @param header header名
* @return header值
*/
default String header(final Header header) {
return header(header.name());
}
/**
* 获取指定的Header值如果不存在返回{@code null}
*
* @param name header名
* @return header值
*/
default String header(final String name) {
final List<String> values = headers().get(name);
if (ArrayUtil.isNotEmpty(values)) {
return values.get(0);
}
return null;
}
/**
* 设置一个header<br>
* 如果覆盖模式,则替换之前的值,否则加入到值列表中
*
* @param name Header名
* @param value Header值
* @param isOverride 是否覆盖已有值
* @return T 本身
*/
default T header(final Header name, final String value, final boolean isOverride) {
return header(name.toString(), value, isOverride);
}
/**
* 设置一个header<br>
* 覆盖模式,则替换之前的值
*
* @param name Header名
* @param value Header值
* @return T 本身
*/
default T header(final Header name, final String value) {
return header(name.toString(), value, true);
}
/**
* 添加请求头,默认覆盖原有头参数
*
* @param name 请求头参数名称
* @param value 参数值
* @return this
*/
default T header(final String name, final String value) {
return header(name, value, true);
}
/**
* 设置contentType
*
* @param contentType contentType
* @return T
*/
default T contentType(final String contentType) {
header(Header.CONTENT_TYPE, contentType);
return (T) this;
}
/**
* 设置是否为长连接
*
* @param isKeepAlive 是否长连接
* @return T
*/
default T keepAlive(final boolean isKeepAlive) {
header(Header.CONNECTION, isKeepAlive ? "Keep-Alive" : "Close");
return (T) this;
}
// endregion ----------------------------------------------------------- headers
// region ----------------------------------------------------------- auth
/**
* 令牌验证,生成的头类似于:"Authorization: Bearer XXXXX"一般用于JWT
*
* @param token 令牌内容
* @return T this
*/
default T bearerAuth(final String token) {
return auth("Bearer " + token);
}
/**
* 验证简单插入Authorization头
*
* @param content 验证内容
* @return T this
*/
default T auth(final String content) {
header(Header.AUTHORIZATION, content, true);
return (T) this;
}
/**
* 验证简单插入Authorization头
*
* @param content 验证内容
* @return T this
*/
default T proxyAuth(final String content) {
header(Header.PROXY_AUTHORIZATION, content, true);
return (T) this;
}
// endregion ----------------------------------------------------------- auth
// region ----------------------------------------------------------- Cookies
/**
* 设置Cookie<br>
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
*
* @param cookies Cookie值数组如果为{@code null}则设置无效使用默认Cookie行为
* @return this
* @since 5.4.1
*/
default T cookie(final Collection<HttpCookie> cookies) {
return cookie(CollUtil.isEmpty(cookies) ? null : cookies.toArray(new HttpCookie[0]));
}
/**
* 设置Cookie<br>
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
*
* @param cookies Cookie值数组如果为{@code null}则设置无效使用默认Cookie行为
* @return this
* @since 3.1.1
*/
default T cookie(final HttpCookie... cookies) {
if (ArrayUtil.isEmpty(cookies)) {
return disableCookie();
}
// 名称/值对之间用分号和空格 ('; ')
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cookie
return cookie(ArrayUtil.join(cookies, "; "));
}
/**
* 设置Cookie<br>
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
*
* @param cookie Cookie值如果为{@code null}则设置无效使用默认Cookie行为
* @return this
* @since 3.0.7
*/
default T cookie(final String cookie) {
return header(Header.COOKIE, cookie, true);
}
/**
* 禁用默认Cookie行为此方法调用后会将Cookie置为空。<br>
* 如果想重新启用Cookie请调用{@link #cookie(String)}方法自定义Cookie。<br>
* 如果想启动默认的Cookie行为自动回填服务器传回的Cookie则调用{@link #enableDefaultCookie()}
*
* @return this
* @since 3.0.7
*/
default T disableCookie() {
return cookie(StrUtil.EMPTY);
}
/**
* 打开默认的Cookie行为自动回填服务器传回的Cookie
*
* @return this
*/
default T enableDefaultCookie() {
return cookie((String) null);
}
// endregion ----------------------------------------------------------- Cookies
}

View File

@@ -1,8 +1,11 @@
package cn.hutool.http;
package cn.hutool.http.client;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import java.io.File;
import java.io.OutputStream;

View File

@@ -0,0 +1,212 @@
package cn.hutool.http.client;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.meta.Header;
import cn.hutool.http.meta.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 请求消息体包括请求的URI、请求头、请求体等
*
* @author looly
* @since 6.0.0
*/
public class Request implements Headers<Request> {
/**
* 构建一个HTTP请求<br>
* 对于传入的URL可以自定义是否解码已经编码的内容设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)}<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数,<br>
* 按照RFC3986规范在发送请求时全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
*
* @param url URL链接默认自动编码URL中的参数等信息
* @return HttpRequest
*/
public static Request of(final String url) {
return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null);
}
/**
* 构建一个HTTP请求<br>
* 对于传入的URL可以自定义是否解码已经编码的内容。<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果charset参数不为{@code null},则会统一解码编码后的参数,<br>
* 按照RFC3986规范在发送请求时全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
*
* @param url URL链接
* @param charset 编码,如果为{@code null}不自动解码编码URL
* @return HttpRequest
*/
public static Request of(final String url, final Charset charset) {
return of(UrlBuilder.ofHttp(url, charset));
}
/**
* 构建一个HTTP请求<br>
*
* @param url {@link UrlBuilder}
* @return HttpRequest
*/
public static Request of(final UrlBuilder url) {
return new Request().url(url);
}
/**
* 默认的请求编码、URL的encode、decode编码
*/
private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
/**
* 请求方法
*/
private Method method = Method.GET;
/**
* 请求的URL
*/
private UrlBuilder url;
/**
* 存储头信息
*/
private final Map<String, List<String>> headers = new HashMap<>();
private RequestBody body;
/**
* 获取Http请求方法
*
* @return {@link Method}
* @since 4.1.8
*/
public Method method() {
return this.method;
}
/**
* 设置请求方法
*
* @param method HTTP方法
* @return HttpRequest
*/
public Request method(final Method method) {
this.method = method;
return this;
}
/**
* 获取请求的URL
*
* @return URL
*/
public UrlBuilder url() {
return url;
}
/**
* 设置URL
*
* @param url URL
* @return this
*/
public Request url(final UrlBuilder url) {
this.url = url;
return this;
}
/**
* 设置编码
*
* @param charset 编码
* @return this
*/
public Request charset(final Charset charset) {
Assert.notNull(this.url, "You must be set request url first.");
this.url.setCharset(charset);
return this;
}
/**
* 获取请求编码,如果用户未设置,返回{@link #DEFAULT_CHARSET}
*
* @return 编码
*/
public Charset charset() {
Assert.notNull(this.url, "You must be set request url first.");
return ObjUtil.defaultIfNull(this.url.getCharset(), DEFAULT_CHARSET);
}
@Override
public Map<String, List<String>> headers() {
return MapUtil.view(this.headers);
}
/**
* 是否为Transfer-Encoding:Chunked的内容
*
* @return 是否为Transfer-Encoding:Chunked的内容
*/
public boolean isChunked() {
final String transferEncoding = header(Header.TRANSFER_ENCODING);
return "Chunked".equalsIgnoreCase(transferEncoding);
}
/**
* 设置一个header<br>
* 如果覆盖模式,则替换之前的值,否则加入到值列表中<br>
* 如果给定值为{@code null},则删除这个头信息
*
* @param name Header名{@code null}跳过
* @param value Header值{@code null}表示删除name对应的头
* @param isOverride 是否覆盖已有值
* @return this
*/
@Override
public Request header(final String name, final String value, final boolean isOverride) {
if (null == name) {
return this;
}
if (null == value) {
headers.remove(name);
return this;
}
final List<String> values = headers.get(name.trim());
if (isOverride || CollUtil.isEmpty(values)) {
final ArrayList<String> valueList = new ArrayList<>();
valueList.add(value);
headers.put(name.trim(), valueList);
} else {
values.add(value.trim());
}
return this;
}
/**
* 获取请求体
*
* @return this
*/
public RequestBody body() {
return this.body;
}
/**
* 添加请求体
*
* @param body 请求体可以是文本、表单、流、byte[] 或 Multipart
* @return this
*/
public Request body(final RequestBody body) {
this.body = body;
return this;
}
}

View File

@@ -0,0 +1,134 @@
package cn.hutool.http.client;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import java.io.Closeable;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* 响应内容接口包括响应状态码、HTTP消息头、响应体等信息
*
* @author looly
* @since 6.0.0
*/
public interface Response extends Closeable {
/**
* 获取状态码
*
* @return 状态码
*/
int getStatus();
/**
* 根据name获取头信息<br>
* 根据RFC2616规范header的name不区分大小写
*
* @param name Header名
* @return Header值
*/
String header(final String name);
/**
* 获取字符集编码
*
* @return 字符集
*/
Charset charset();
/**
* 获得服务区响应流<br>
* 流获取后处理完毕需关闭此类
*
* @return 响应流
*/
InputStream bodyStream();
/**
* 获取响应主体
*
* @return String
* @throws HttpException 包装IO异常
*/
default String body() throws HttpException {
return HttpUtil.getString(bodyStream(), charset(), true);
}
/**
* 请求是否成功判断依据为状态码范围在200~299内。
*
* @return 是否成功请求
*/
default boolean isOk() {
final int status = getStatus();
return status >= 200 && status < 300;
}
/**
* 根据name获取头信息
*
* @param name Header名
* @return Header值
*/
default String header(final Header name) {
if (null == name) {
return null;
}
return header(name.toString());
}
/**
* 获取内容编码
*
* @return String
*/
default String contentEncoding() {
return header(Header.CONTENT_ENCODING);
}
/**
* 获取内容长度,以下情况长度无效:
* <ul>
* <li>Transfer-Encoding: Chunked</li>
* <li>Content-Encoding: XXX</li>
* </ul>
* 参考:<a href="https://blog.csdn.net/jiang7701037/article/details/86304302">https://blog.csdn.net/jiang7701037/article/details/86304302</a>
*
* @return 长度,-1表示服务端未返回或长度无效
* @since 5.7.9
*/
default long contentLength() {
long contentLength = Convert.toLong(header(Header.CONTENT_LENGTH), -1L);
if (contentLength > 0 && (isChunked() || StrUtil.isNotBlank(contentEncoding()))) {
//按照HTTP协议规范在 Transfer-Encoding和Content-Encoding设置后 Content-Length 无效。
contentLength = -1;
}
return contentLength;
}
/**
* 是否为Transfer-Encoding:Chunked的内容
*
* @return 是否为Transfer-Encoding:Chunked的内容
* @since 4.6.2
*/
default boolean isChunked() {
final String transferEncoding = header(Header.TRANSFER_ENCODING);
return "Chunked".equalsIgnoreCase(transferEncoding);
}
/**
* 获取本次请求服务器返回的Cookie信息
*
* @return Cookie字符串
* @since 3.1.1
*/
default String getCookieStr() {
return header(Header.SET_COOKIE);
}
}

View File

@@ -1,8 +1,6 @@
package cn.hutool.http.body;
package cn.hutool.http.client.body;
import cn.hutool.core.io.IoUtil;
import java.io.OutputStream;
import cn.hutool.core.io.resource.BytesResource;
/**
* bytes类型的Http request body主要发送编码后的表单数据或rest body如JSON或XML
@@ -10,9 +8,7 @@ import java.io.OutputStream;
* @since 5.7.17
* @author looly
*/
public class BytesBody implements RequestBody {
private final byte[] content;
public class BytesBody extends ResourceBody {
/**
* 创建 Http request body
@@ -29,11 +25,6 @@ public class BytesBody implements RequestBody {
* @param content Body内容编码后
*/
public BytesBody(final byte[] content) {
this.content = content;
}
@Override
public void write(final OutputStream out) {
IoUtil.write(out, false, content);
super(new BytesResource(content));
}
}

View File

@@ -1,4 +1,4 @@
package cn.hutool.http.body;
package cn.hutool.http.client.body;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.text.StrUtil;

View File

@@ -1,10 +1,9 @@
package cn.hutool.http.body;
package cn.hutool.http.client.body;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.http.MultipartOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.client.body;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IORuntimeException;
@@ -7,6 +7,10 @@ import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.io.resource.StringResource;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.core.io.resource.HttpResource;
import cn.hutool.http.HttpUtil;
import java.io.IOException;
import java.io.OutputStream;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http.body;
package cn.hutool.http.client.body;
import cn.hutool.core.io.IoUtil;

View File

@@ -0,0 +1,49 @@
package cn.hutool.http.client.body;
import cn.hutool.core.io.resource.Resource;
import java.io.OutputStream;
/**
* {@link Resource}类型的Http request body主要发送资源文件中的内容
*
* @author looly
* @since 6.0.0
*/
public class ResourceBody implements RequestBody {
private final Resource resource;
/**
* 创建 Http request body
*
* @param resource body内容
* @return BytesBody
*/
public static ResourceBody of(final Resource resource) {
return new ResourceBody(resource);
}
/**
* 构造
*
* @param resource Body内容
*/
public ResourceBody(final Resource resource) {
this.resource = resource;
}
/**
* 获取资源
*
* @return 资源
*/
public Resource getResource() {
return this.resource;
}
@Override
public void write(final OutputStream out) {
resource.writeTo(out);
}
}

View File

@@ -1,7 +1,7 @@
/**
* 请求体封装实现
*
*
* @author looly
*
*/
package cn.hutool.http.body;
package cn.hutool.http.client.body;

View File

@@ -1,9 +1,9 @@
package cn.hutool.http.cookie;
package cn.hutool.http.client.cookie;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.http.HttpConnection;
import cn.hutool.http.client.engine.jdk.HttpConnection;
import java.io.IOException;
import java.net.CookieManager;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http.cookie;
package cn.hutool.http.client.cookie;
import java.net.CookieManager;
import java.net.CookieStore;

View File

@@ -1,7 +1,7 @@
/**
* 自定义Cookie
*
*
* @author looly
*
*/
package cn.hutool.http.cookie;
package cn.hutool.http.client.cookie;

View File

@@ -0,0 +1,13 @@
package cn.hutool.http.client.engine;
import cn.hutool.http.client.ClientEngine;
/**
* Http客户端引擎工厂类
*/
public class EngineFactory {
public static ClientEngine getEngine(){
return null;
}
}

View File

@@ -0,0 +1,112 @@
package cn.hutool.http.client.engine.httpclient4;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.http.GlobalHeaders;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.ClientEngine;
import cn.hutool.http.client.Request;
import cn.hutool.http.client.Response;
import cn.hutool.http.client.body.RequestBody;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Apache HttpClient5的HTTP请求引擎
*
* @author looly
* @since 6.0.0
*/
public class HttpClient4Engine implements ClientEngine {
private final CloseableHttpClient engine;
/**
* 构造
*/
public HttpClient4Engine() {
this.engine = HttpClients.custom()
// 设置默认头信息
.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers()))
.build();
}
@Override
public Response send(final Request message) {
final HttpEntityEnclosingRequestBase request = buildRequest(message);
final CloseableHttpResponse response;
try {
response = this.engine.execute(request);
} catch (final IOException e) {
throw new HttpException(e);
}
return new HttpClient4Response(response, message.charset());
}
@Override
public Object getRawEngine() {
return this.engine;
}
@Override
public void close() throws IOException {
this.engine.close();
}
/**
* 构建请求体
*
* @param message {@link Request}
* @return {@link HttpEntityEnclosingRequestBase}
*/
private static HttpEntityEnclosingRequestBase buildRequest(final Request message) {
final UrlBuilder url = message.url();
Assert.notNull(url, "Request URL must be not null!");
final URI uri = url.toURI();
final HttpEntityEnclosingRequestBase request = new HttpEntityEnclosingRequestBase() {
@Override
public String getMethod() {
return message.method().name();
}
};
request.setURI(uri);
// 填充自定义头
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
// 填充自定义消息体
final RequestBody body = message.body();
request.setEntity(new RequestBodyEntity(
// 用户自定义的内容类型
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
// 用户自定义编码
message.charset(),
message.isChunked(),
body));
return request;
}
/**
* 获取默认头列表
*
* @return 默认头列表
*/
private static List<Header> toHeaderList(final Map<String, List<String>> headersMap) {
final List<Header> result = new ArrayList<>();
headersMap.forEach((k, v1) -> v1.forEach((v2) -> result.add(new BasicHeader(k, v2))));
return result;
}
}

View File

@@ -0,0 +1,98 @@
package cn.hutool.http.client.engine.httpclient4;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.Response;
import org.apache.http.Header;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* HttpClient响应包装<br>
* 通过包装{@link CloseableHttpResponse},实现获取响应状态码、响应头、响应体等内容
*
* @author looly
*/
public class HttpClient4Response implements Response {
/**
* HttpClient的响应对象
*/
private final CloseableHttpResponse rawRes;
/**
* 请求时的默认编码
*/
private final Charset requestCharset;
/**
* 构造<br>
* 通过传入一个请求时的编码,当无法获取响应内容的编码时,默认使用响应时的编码
*
* @param rawRes {@link CloseableHttpResponse}
* @param requestCharset 请求时的编码
*/
public HttpClient4Response(final CloseableHttpResponse rawRes, final Charset requestCharset) {
this.rawRes = rawRes;
this.requestCharset = requestCharset;
}
@Override
public int getStatus() {
return rawRes.getStatusLine().getStatusCode();
}
@Override
public String header(final String name) {
final Header[] headers = rawRes.getHeaders(name);
if (ArrayUtil.isNotEmpty(headers)) {
return headers[0].getValue();
}
return null;
}
@Override
public long contentLength() {
return rawRes.getEntity().getContentLength();
}
@Override
public Charset charset() {
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType().getValue()).getCharset();
return ObjUtil.defaultIfNull(charset, requestCharset);
}
@Override
public InputStream bodyStream() {
try {
return rawRes.getEntity().getContent();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
@Override
public String body() throws HttpException {
try {
return EntityUtils.toString(rawRes.getEntity(), charset());
} catch (final IOException e) {
throw new IORuntimeException(e);
} catch (final ParseException e) {
throw new HttpException(e);
}
}
@Override
public void close() throws IOException {
rawRes.close();
}
}

View File

@@ -0,0 +1,72 @@
package cn.hutool.http.client.engine.httpclient4;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.http.client.body.BytesBody;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.client.body.ResourceBody;
import org.apache.http.entity.AbstractHttpEntity;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* {@link RequestBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
*
* @author looly
* @since 6.0.0
*/
public class RequestBodyEntity extends AbstractHttpEntity {
private final RequestBody body;
/**
* 构造
*
* @param contentType Content-Type类型
* @param charset 自定义请求编码
* @param chunked 是否块模式传输
* @param body {@link RequestBody}
*/
public RequestBodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
super();
setContentType(contentType);
setContentEncoding(null == charset ? null : charset.name());
setChunked(chunked);
this.body = body;
}
@Override
public void writeTo(final OutputStream outStream) {
if (null != body) {
body.writeClose(outStream);
}
}
@Override
public InputStream getContent() {
if (body instanceof ResourceBody) {
return ((ResourceBody) body).getResource().getStream();
} else {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
body.writeClose(out);
return new ByteArrayInputStream(out.toByteArray());
}
}
@Override
public boolean isStreaming() {
return body instanceof BytesBody;
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public long getContentLength() {
return 0;
}
}

View File

@@ -0,0 +1,7 @@
/**
* Apache HttpClient 4.x实现<br>
* 文档见https://hc.apache.org/httpcomponents-client-4.5.x/index.html
*
* @author looly
*/
package cn.hutool.http.client.engine.httpclient4;

View File

@@ -0,0 +1,66 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0.M1</version>
</parent>
<artifactId>hutool-http</artifactId>
<name>${project.artifactId}</name>
<description>Hutool Http客户端</description>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<!-- 第三方HTTP客户端库 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>0.1.2</version>
<scope>test</scope>
</dependency>
<!-- 仅用于测试 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,108 @@
package cn.hutool.http.client.engine.httpclient5;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.http.GlobalHeaders;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.ClientEngine;
import cn.hutool.http.client.Request;
import cn.hutool.http.client.Response;
import cn.hutool.http.client.body.RequestBody;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Apache HttpClient5的HTTP请求引擎
*
* @author looly
* @since 6.0.0
*/
public class HttpClient5Engine implements ClientEngine {
private final CloseableHttpClient engine;
/**
* 构造
*/
public HttpClient5Engine() {
this.engine = HttpClients.custom()
// 设置默认头信息
.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers()))
.build();
}
@Override
public Response send(final Request message) {
final ClassicHttpRequest request = buildRequest(message);
final CloseableHttpResponse response;
try {
response = this.engine.execute(request);
} catch (final IOException e) {
throw new HttpException(e);
}
return new HttpClient5Response(response, message.charset());
}
@Override
public Object getRawEngine() {
return this.engine;
}
@Override
public void close() throws IOException {
this.engine.close();
}
/**
* 构建请求体
*
* @param message {@link Request}
* @return {@link ClassicHttpRequest}
*/
@SuppressWarnings("ConstantConditions")
private static ClassicHttpRequest buildRequest(final Request message) {
final UrlBuilder url = message.url();
Assert.notNull(url, "Request URL must be not null!");
final URI uri = url.toURI();
final ClassicHttpRequest request = new HttpUriRequestBase(message.method().name(), uri);
// 填充自定义头
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
// 填充自定义消息体
final RequestBody body = message.body();
request.setEntity(new RequestBodyEntity(
// 用户自定义的内容类型
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
// 用户自定义编码
message.charset(),
message.isChunked(),
body));
return request;
}
/**
* 获取默认头列表
*
* @return 默认头列表
*/
private static List<Header> toHeaderList(final Map<String, List<String>> headersMap) {
final List<Header> result = new ArrayList<>();
headersMap.forEach((k, v1) -> v1.forEach((v2) -> result.add(new BasicHeader(k, v2))));
return result;
}
}

View File

@@ -0,0 +1,98 @@
package cn.hutool.http.client.engine.httpclient5;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.Response;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* HttpClient响应包装<br>
* 通过包装{@link CloseableHttpResponse},实现获取响应状态码、响应头、响应体等内容
*
* @author looly
*/
public class HttpClient5Response implements Response {
/**
* HttpClient的响应对象
*/
private final CloseableHttpResponse rawRes;
/**
* 请求时的默认编码
*/
private final Charset requestCharset;
/**
* 构造<br>
* 通过传入一个请求时的编码,当无法获取响应内容的编码时,默认使用响应时的编码
*
* @param rawRes {@link CloseableHttpResponse}
* @param requestCharset 请求时的编码
*/
public HttpClient5Response(final CloseableHttpResponse rawRes, final Charset requestCharset) {
this.rawRes = rawRes;
this.requestCharset = requestCharset;
}
@Override
public int getStatus() {
return rawRes.getCode();
}
@Override
public String header(final String name) {
final Header[] headers = rawRes.getHeaders(name);
if (ArrayUtil.isNotEmpty(headers)) {
return headers[0].getValue();
}
return null;
}
@Override
public long contentLength() {
return rawRes.getEntity().getContentLength();
}
@Override
public Charset charset() {
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType()).getCharset();
return ObjUtil.defaultIfNull(charset, requestCharset);
}
@Override
public InputStream bodyStream() {
try {
return rawRes.getEntity().getContent();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
@Override
public String body() throws HttpException {
try {
return EntityUtils.toString(rawRes.getEntity(), charset());
} catch (final IOException e) {
throw new IORuntimeException(e);
} catch (final ParseException e) {
throw new HttpException(e);
}
}
@Override
public void close() throws IOException {
rawRes.close();
}
}

View File

@@ -0,0 +1,70 @@
package cn.hutool.http.client.engine.httpclient5;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.http.client.body.BytesBody;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.client.body.ResourceBody;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* {@link RequestBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
*
* @author looly
* @since 6.0.0
*/
public class RequestBodyEntity extends AbstractHttpEntity {
private final RequestBody body;
/**
* 构造
*
* @param contentType Content-Type类型
* @param charset 自定义请求编码
* @param chunked 是否块模式传输
* @param body {@link RequestBody}
*/
public RequestBodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
super(contentType, null == charset ? null : charset.name(), chunked);
this.body = body;
}
@Override
public void writeTo(final OutputStream outStream) {
if(null != body){
body.writeClose(outStream);
}
}
@Override
public InputStream getContent() {
if (body instanceof ResourceBody) {
return ((ResourceBody) body).getResource().getStream();
} else {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
body.writeClose(out);
return new ByteArrayInputStream(out.toByteArray());
}
}
@Override
public boolean isStreaming() {
return body instanceof BytesBody;
}
@Override
public void close() throws IOException {
}
@Override
public long getContentLength() {
return 0;
}
}

View File

@@ -0,0 +1,7 @@
/**
* Apache HttpClient 5.1.x实现<br>
* 文档见https://hc.apache.org/httpcomponents-client-5.1.x/index.html
*
* @author looly
*/
package cn.hutool.http.client.engine.httpclient5;

View File

@@ -1,10 +1,12 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.meta.Header;
import cn.hutool.http.client.Headers;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -21,7 +23,7 @@ import java.util.Map.Entry;
* @author Looly
*/
@SuppressWarnings("unchecked")
public abstract class HttpBase<T> {
public abstract class HttpBase<T extends HttpBase<T>> implements Headers<T> {
/**
* 默认的请求编码URL的encodedecode编码
@@ -56,21 +58,6 @@ public abstract class HttpBase<T> {
// ---------------------------------------------------------------- Headers start
/**
* 根据name获取头信息<br>
* 根据RFC2616规范header的name不区分大小写
*
* @param name Header名
* @return Header值
*/
public String header(final String name) {
final List<String> values = headerList(name);
if (CollUtil.isEmpty(values)) {
return null;
}
return values.get(0);
}
/**
* 根据name获取头信息列表
*
@@ -90,14 +77,14 @@ public abstract class HttpBase<T> {
/**
* 根据name获取头信息
*
* @param name Header名
* @param header Header名
* @return Header值
*/
public String header(final Header name) {
if (null == name) {
public String header(final Header header) {
if (null == header) {
return null;
}
return header(name.toString());
return header(header.toString());
}
/**
@@ -109,6 +96,7 @@ public abstract class HttpBase<T> {
* @param isOverride 是否覆盖已有值
* @return T 本身
*/
@Override
public T header(final String name, final String value, final boolean isOverride) {
if (null != name && null != value) {
final List<String> values = headers.get(name.trim());
@@ -123,43 +111,6 @@ public abstract class HttpBase<T> {
return (T) this;
}
/**
* 设置一个header<br>
* 如果覆盖模式则替换之前的值否则加入到值列表中
*
* @param name Header名
* @param value Header值
* @param isOverride 是否覆盖已有值
* @return T 本身
*/
public T header(final Header name, final String value, final boolean isOverride) {
return header(name.toString(), value, isOverride);
}
/**
* 设置一个header<br>
* 覆盖模式则替换之前的值
*
* @param name Header名
* @param value Header值
* @return T 本身
*/
public T header(final Header name, final String value) {
return header(name.toString(), value, true);
}
/**
* 设置一个header<br>
* 覆盖模式则替换之前的值
*
* @param name Header名
* @param value Header值
* @return T 本身
*/
public T header(final String name, final String value) {
return header(name, value, true);
}
/**
* 设置请求头
*
@@ -260,6 +211,7 @@ public abstract class HttpBase<T> {
*
* @return Headers Map
*/
@Override
public Map<String, List<String>> headers() {
return Collections.unmodifiableMap(headers);
}

View File

@@ -1,10 +1,15 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.meta.Method;
import cn.hutool.http.ssl.DefaultSSLInfo;
import javax.net.ssl.HostnameVerifier;

View File

@@ -1,7 +1,10 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.reflect.ConstructorUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.GlobalCompressStreamRegister;
import cn.hutool.http.HttpException;
import cn.hutool.http.meta.HttpStatus;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import java.util.Iterator;
import java.util.LinkedList;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
@@ -16,11 +16,20 @@ import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.body.BytesBody;
import cn.hutool.http.body.FormUrlEncodedBody;
import cn.hutool.http.body.MultipartBody;
import cn.hutool.http.body.RequestBody;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.GlobalHeaders;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpConfig;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.http.meta.HttpStatus;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.meta.Method;
import cn.hutool.http.client.body.BytesBody;
import cn.hutool.http.client.body.FormUrlEncodedBody;
import cn.hutool.http.client.body.MultipartBody;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.client.cookie.GlobalCookieManager;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
@@ -255,7 +264,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
this.charset(charset);
}
// 给定一个默认头信息
this.header(GlobalHeaders.INSTANCE.headers);
this.header(GlobalHeaders.INSTANCE.headers());
}
/**
@@ -309,28 +318,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
return this;
}
/**
* 获取Http请求方法
*
* @return {@link Method}
* @since 4.1.8
*/
public Method getMethod() {
return this.method;
}
/**
* 设置请求方法
*
* @param method HTTP方法
* @return HttpRequest
* @see #method(Method)
* @since 4.1.8
*/
public HttpRequest setMethod(final Method method) {
return method(method);
}
/**
* 获取{@link HttpConnection}<br>
* {@link #execute()} 执行前此对象为null
@@ -342,6 +329,16 @@ public class HttpRequest extends HttpBase<HttpRequest> {
return this.httpConnection;
}
/**
* 获取Http请求方法
*
* @return {@link Method}
* @since 4.1.8
*/
public Method getMethod() {
return this.method;
}
/**
* 设置请求方法
*

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FastByteArrayOutputStream;
@@ -11,7 +11,11 @@ import cn.hutool.core.net.url.URLEncoder;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpConfig;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.client.cookie.GlobalCookieManager;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
@@ -261,7 +265,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
Assert.notNull(out, "[out] must be not null!");
final long contentLength = contentLength();
try {
return copyBody(bodyStream(), out, contentLength, streamProgress, this.config.ignoreEOFError);
return copyBody(bodyStream(), out, contentLength, streamProgress, this.config.isIgnoreEOFError());
} finally {
IoUtil.close(this);
if (isCloseOut) {
@@ -587,7 +591,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
final long contentLength = contentLength();
final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength);
copyBody(in, out, contentLength, null, this.config.ignoreEOFError);
copyBody(in, out, contentLength, null, this.config.isIgnoreEOFError());
this.bodyBytes = out.toByteArray();
}

View File

@@ -0,0 +1,7 @@
/**
* 基于JDK的HttpUrlConnection封装的HTTP客户端
*
* @author looly
* @since 6.0.0
*/
package cn.hutool.http.client.engine.jdk;

View File

@@ -0,0 +1,70 @@
package cn.hutool.http.client.engine.okhttp;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.http.client.ClientEngine;
import cn.hutool.http.client.Request;
import cn.hutool.http.client.Response;
import okhttp3.OkHttpClient;
import okhttp3.internal.http.HttpMethod;
import java.io.IOException;
/**
* OkHttp3客户端引擎封装
*
* @author looly
* @since 6.0.0
*/
public class OkHttpEngine implements ClientEngine {
private final OkHttpClient client;
/**
* 构造
*/
public OkHttpEngine() {
this.client = new OkHttpClient();
}
@Override
public Response send(final Request message) {
final okhttp3.Response response;
try {
response = client.newCall(buildRequest(message)).execute();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return new OkHttpResponse(response, message.charset());
}
@Override
public Object getRawEngine() {
return this.client;
}
@Override
public void close() throws IOException {
// ignore
}
/**
* 构建请求体
*
* @param message {@link Request}
* @return {@link okhttp3.Request}
*/
private static okhttp3.Request buildRequest(final Request message) {
final okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
.url(message.url().toURL());
final String method = message.method().name();
if(HttpMethod.permitsRequestBody(method)){
builder.method(method, new OkHttpRequestBody(message.body()));
}else{
builder.method(method, null);
}
return builder.build();
}
}

View File

@@ -0,0 +1,33 @@
package cn.hutool.http.client.engine.okhttp;
import cn.hutool.http.client.body.RequestBody;
import okhttp3.MediaType;
import okio.BufferedSink;
/**
* OkHttp的请求体实现通过{@link RequestBody}转换实现
*
* @author looly
*/
public class OkHttpRequestBody extends okhttp3.RequestBody {
private final RequestBody body;
/**
* 构造
*
* @param body 请求体{@link RequestBody}
*/
public OkHttpRequestBody(final RequestBody body) {
this.body = body;
}
public MediaType contentType() {
return null;
}
@Override
public void writeTo(final BufferedSink bufferedSink) {
body.writeClose(bufferedSink.outputStream());
}
}

View File

@@ -0,0 +1,70 @@
package cn.hutool.http.client.engine.okhttp;
import cn.hutool.core.io.EmptyInputStream;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.client.Response;
import cn.hutool.http.meta.Header;
import okhttp3.ResponseBody;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* OkHttp3的{@link okhttp3.Response} 响应包装
*
* @author looly
*/
public class OkHttpResponse implements Response {
private final okhttp3.Response rawRes;
/**
* 请求时的默认编码
*/
private final Charset requestCharset;
/**
* @param rawRes {@link okhttp3.Response}
* @param requestCharset 请求时的默认编码
*/
public OkHttpResponse(final okhttp3.Response rawRes, final Charset requestCharset) {
this.rawRes = rawRes;
this.requestCharset = requestCharset;
}
@Override
public int getStatus() {
return rawRes.code();
}
@Override
public String header(final String name) {
return rawRes.header(name);
}
@Override
public Charset charset() {
final String contentType = rawRes.header(Header.CONTENT_TYPE.getValue());
if(StrUtil.isNotEmpty(contentType)){
final String charset = HttpUtil.getCharset(contentType);
CharsetUtil.parse(charset, this.requestCharset);
}
return this.requestCharset;
}
@Override
public InputStream bodyStream() {
final ResponseBody body = rawRes.body();
if(null == body){
return EmptyInputStream.INSTANCE;
}
return body.byteStream();
}
@Override
public void close() throws IOException {
rawRes.close();
}
}

View File

@@ -0,0 +1,5 @@
/**
* OKHttp3封装<br>
* 文档见https://square.github.io/okhttp/
*/
package cn.hutool.http.client.engine.okhttp;

View File

@@ -0,0 +1,6 @@
/**
* Http客户端引擎实现
*
* @author looly
*/
package cn.hutool.http.client.engine;

View File

@@ -0,0 +1,5 @@
/**
* HTTP请求客户端封装
* @author looly
*/
package cn.hutool.http.client;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.meta;
import cn.hutool.core.text.StrUtil;

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.meta;
/**
* Http 头域

View File

@@ -1,11 +1,16 @@
package cn.hutool.http;
package cn.hutool.http.meta;
/**
* 返回状态码
* HTTP状态码
*
* @author Looly
* @see java.net.HttpURLConnection
*
*/
interface Status {
public interface HttpStatus {
/* 2XX: generally "OK" */
/**
* HTTP Status-Code 200: OK.
*/
@@ -73,6 +78,18 @@ interface Status {
*/
int HTTP_USE_PROXY = 305;
/**
* HTTP 1.1 Status-Code 307: Temporary Redirect.<br>
* RFC-7231
*/
int HTTP_TEMP_REDIRECT = 307;
/**
* HTTP 1.1 Status-Code 308: Permanent Redirect 永久重定向<br>
* RFC-7231
*/
int HTTP_PERMANENT_REDIRECT = 308;
/* 4XX: client error */
/**
@@ -186,4 +203,20 @@ interface Status {
* HTTP Status-Code 505: HTTP Version Not Supported.
*/
int HTTP_VERSION = 505;
/**
* 是否为重定向状态码
* @param responseCode 被检查的状态码
* @return 是否为重定向状态码
* @since 5.6.3
*/
static boolean isRedirected(final int responseCode){
return responseCode == HTTP_MOVED_PERM
|| responseCode == HTTP_MOVED_TEMP
|| responseCode == HTTP_SEE_OTHER
// issue#1504@Github307和308是RFC 7538中http 1.1定义的规范
|| responseCode == HTTP_TEMP_REDIRECT
|| responseCode == HTTP_PERMANENT_REDIRECT;
}
}

View File

@@ -1,4 +1,4 @@
package cn.hutool.http;
package cn.hutool.http.meta;
/**
* Http方法枚举

View File

@@ -0,0 +1,6 @@
/**
* Http元数据信息包括Header枚举、状态码、Http方法、枚举Content-Type等
*
* @author looly
*/
package cn.hutool.http.meta;

View File

@@ -11,9 +11,9 @@ import cn.hutool.core.net.multipart.UploadSetting;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.Header;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import cn.hutool.http.meta.Method;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.sun.net.httpserver.Headers;

View File

@@ -6,9 +6,9 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.url.URLEncoder;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.meta.Header;
import cn.hutool.http.meta.HttpStatus;
import cn.hutool.http.HttpUtil;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;

View File

@@ -7,30 +7,32 @@ import java.util.List;
import java.util.regex.Pattern;
/**
* 引擎对象
* 浏览器引擎对象
*
* @author looly
* @since 4.2.1
*/
public class Engine extends UserAgentInfo {
public class BrowserEngine extends UserAgentInfo {
private static final long serialVersionUID = 1L;
/** 未知 */
public static final Engine Unknown = new Engine(NameUnknown, null);
/**
* 未知
*/
public static final BrowserEngine Unknown = new BrowserEngine(NameUnknown, null);
/**
* 支持的引擎类型
*/
public static final List<Engine> engines = ListUtil.view(
new Engine("Trident", "trident"),
new Engine("Webkit", "webkit"),
new Engine("Chrome", "chrome"),
new Engine("Opera", "opera"),
new Engine("Presto", "presto"),
new Engine("Gecko", "gecko"),
new Engine("KHTML", "khtml"),
new Engine("Konqueror", "konqueror"),
new Engine("MIDP", "MIDP")
public static final List<BrowserEngine> engines = ListUtil.view(
new BrowserEngine("Trident", "trident"),
new BrowserEngine("Webkit", "webkit"),
new BrowserEngine("Chrome", "chrome"),
new BrowserEngine("Opera", "opera"),
new BrowserEngine("Presto", "presto"),
new BrowserEngine("Gecko", "gecko"),
new BrowserEngine("KHTML", "khtml"),
new BrowserEngine("Konqueror", "konqueror"),
new BrowserEngine("MIDP", "MIDP")
);
private final Pattern versionPattern;
@@ -38,10 +40,10 @@ public class Engine extends UserAgentInfo {
/**
* 构造
*
* @param name 引擎名称
* @param name 引擎名称
* @param regex 关键字或表达式
*/
public Engine(final String name, final String regex) {
public BrowserEngine(final String name, final String regex) {
super(name, regex);
this.versionPattern = Pattern.compile(name + "[/\\- ]([\\w.\\-]+)", Pattern.CASE_INSENSITIVE);
}
@@ -54,7 +56,7 @@ public class Engine extends UserAgentInfo {
* @since 5.7.4
*/
public String getVersion(final String userAgentString) {
if(isUnknown()){
if (isUnknown()) {
return null;
}
return ReUtil.getGroup1(this.versionPattern, userAgentString);

View File

@@ -41,7 +41,7 @@ public class UserAgent implements Serializable {
/**
* 引擎类型
*/
private Engine engine;
private BrowserEngine engine;
/**
* 引擎版本
*/
@@ -144,7 +144,7 @@ public class UserAgent implements Serializable {
*
* @return 引擎类型
*/
public Engine getEngine() {
public BrowserEngine getEngine() {
return engine;
}
@@ -153,7 +153,7 @@ public class UserAgent implements Serializable {
*
* @param engine 引擎类型
*/
public void setEngine(final Engine engine) {
public void setEngine(final BrowserEngine engine) {
this.engine = engine;
}

View File

@@ -28,7 +28,7 @@ public class UserAgentParser {
userAgent.setVersion(browser.getVersion(userAgentString));
// 浏览器引擎
final Engine engine = parseEngine(userAgentString);
final BrowserEngine engine = parseEngine(userAgentString);
userAgent.setEngine(engine);
userAgent.setEngineVersion(engine.getVersion(userAgentString));
@@ -67,13 +67,13 @@ public class UserAgentParser {
* @param userAgentString User-Agent字符串
* @return 引擎类型
*/
private static Engine parseEngine(final String userAgentString) {
for (final Engine engine : Engine.engines) {
private static BrowserEngine parseEngine(final String userAgentString) {
for (final BrowserEngine engine : BrowserEngine.engines) {
if (engine.isMatch(userAgentString)) {
return engine;
}
}
return Engine.Unknown;
return BrowserEngine.Unknown;
}
/**

View File

@@ -6,10 +6,10 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.http.HttpBase;
import cn.hutool.http.client.engine.jdk.HttpBase;
import cn.hutool.http.HttpGlobalConfig;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

View File

@@ -1,6 +1,7 @@
package cn.hutool.http;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.meta.ContentType;
import org.junit.Assert;
import org.junit.Test;

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -7,6 +7,10 @@ import cn.hutool.core.lang.Console;
import cn.hutool.core.net.ssl.SSLProtocols;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import cn.hutool.http.meta.Header;
import cn.hutool.http.meta.Method;
import org.junit.Ignore;
import org.junit.Test;
@@ -213,7 +217,7 @@ public class HttpRequestTest {
urlBuilder.setScheme("https").setHost("hutool.cn");
final HttpRequest httpRequest = new HttpRequest(urlBuilder);
httpRequest.setMethod(Method.GET).execute();
httpRequest.method(Method.GET).execute();
}
@Test

View File

@@ -5,6 +5,9 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.meta.Header;
import cn.hutool.http.meta.Method;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -3,6 +3,8 @@ package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -1,6 +1,8 @@
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import cn.hutool.http.meta.Header;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -1,5 +1,6 @@
package cn.hutool.http;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.json.JSONUtil;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -1,6 +1,8 @@
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import cn.hutool.http.meta.Header;
import org.brotli.dec.BrotliInputStream;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -1,6 +1,8 @@
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.meta.Header;
import cn.hutool.json.JSONUtil;
import org.junit.Assert;
import org.junit.Ignore;
@@ -20,7 +22,7 @@ public class RestTest {
.body(JSONUtil.ofObj()
.set("aaa", "aaaValue")
.set("键2", "值2").toString());
Assert.assertEquals("application/json;charset=UTF-8", request.header("Content-Type"));
Assert.assertEquals("application/json;charset=UTF-8", request.header(Header.CONTENT_TYPE));
}
@Test

View File

@@ -2,6 +2,9 @@ package cn.hutool.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.HttpRequest;
import cn.hutool.http.client.engine.jdk.HttpResponse;
import cn.hutool.http.meta.Header;
import org.junit.Ignore;
import org.junit.Test;

View File

@@ -2,7 +2,8 @@ package cn.hutool.http.body;
import cn.hutool.core.io.resource.StringResource;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpResource;
import cn.hutool.core.io.resource.HttpResource;
import cn.hutool.http.client.body.MultipartBody;
import org.junit.Assert;
import org.junit.Test;

View File

@@ -0,0 +1,23 @@
package cn.hutool.http.client;
import cn.hutool.core.lang.Console;
import cn.hutool.http.meta.Method;
import cn.hutool.http.client.engine.httpclient4.HttpClient4Engine;
import org.junit.Ignore;
import org.junit.Test;
public class HttpClient4EngineTest {
@SuppressWarnings("resource")
@Test
@Ignore
public void getTest() {
final ClientEngine engine = new HttpClient4Engine();
final Request req = Request.of("https://www.hutool.cn/").method(Method.GET);
final Response res = engine.send(req);
Console.log(res.getStatus());
Console.log(res.body());
}
}

View File

@@ -0,0 +1,23 @@
package cn.hutool.http.client;
import cn.hutool.core.lang.Console;
import cn.hutool.http.meta.Method;
import cn.hutool.http.client.engine.httpclient5.HttpClient5Engine;
import org.junit.Ignore;
import org.junit.Test;
public class HttpClient5EngineTest {
@SuppressWarnings("resource")
@Test
@Ignore
public void getTest() {
final ClientEngine engine = new HttpClient5Engine();
final Request req = Request.of("https://www.hutool.cn/").method(Method.GET);
final Response res = engine.send(req);
Console.log(res.getStatus());
Console.log(res.body());
}
}

View File

@@ -0,0 +1,23 @@
package cn.hutool.http.client;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.okhttp.OkHttpEngine;
import cn.hutool.http.meta.Method;
import org.junit.Ignore;
import org.junit.Test;
public class OkHttpEngineTest {
@SuppressWarnings("resource")
@Test
@Ignore
public void getTest(){
final ClientEngine engine = new OkHttpEngine();
final Request req = Request.of("https://www.hutool.cn/").method(Method.GET);
final Response res = engine.send(req);
Console.log(res.getStatus());
Console.log(res.body());
}
}

View File

@@ -1,6 +1,6 @@
package cn.hutool.http.server;
import cn.hutool.http.ContentType;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.HttpUtil;
public class BlankServerTest {

View File

@@ -4,8 +4,8 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.net.multipart.UploadFile;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.meta.ContentType;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;