fix #I85C9S@Gitee

This commit is contained in:
Looly 2023-10-06 21:24:52 +08:00
parent 6c39380b6a
commit 74916a4c07
9 changed files with 181 additions and 72 deletions

View File

@ -17,11 +17,13 @@ import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.map.multi.ListValueMap;
import org.dromara.hutool.core.net.url.UrlBuilder;
import org.dromara.hutool.core.net.url.UrlQuery;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.http.GlobalHeaders;
import org.dromara.hutool.http.HttpGlobalConfig;
import org.dromara.hutool.http.client.body.FormBody;
import org.dromara.hutool.http.client.body.HttpBody;
import org.dromara.hutool.http.client.body.StringBody;
import org.dromara.hutool.http.client.body.UrlEncodedFormBody;
@ -105,6 +107,10 @@ public class Request implements HeaderOperation<Request> {
* 最大重定向次数
*/
private int maxRedirectCount;
/**
* 是否是REST请求模式REST模式运行GET请求附带body
*/
private boolean isRest;
/**
* 默认构造
@ -148,6 +154,15 @@ public class Request implements HeaderOperation<Request> {
return url;
}
/**
* 获取处理后的请求URL即如果为非REST的GET请求将form类型的body拼接为URL的一部分
*
* @return URL
*/
public UrlBuilder handledUrl() {
return urlWithParamIfGet();
}
/**
* 设置URL
*
@ -227,12 +242,24 @@ public class Request implements HeaderOperation<Request> {
/**
* 获取请求体
*
* @return this
* @return 请求体
*/
public HttpBody body() {
return this.body;
}
/**
* 获取处理过的请求体即如果是非REST的GET请求始终返回{@code null}
*
* @return 请求体
*/
public HttpBody handledBody() {
if (Method.GET.equals(method) && !this.isRest) {
return null;
}
return body();
}
/**
* 添加请求表单内容
*
@ -291,6 +318,18 @@ public class Request implements HeaderOperation<Request> {
return this;
}
/**
* 设置是否rest模式<br>
* rest模式下get请求不会把参数附加到URL之后而是作为body发送
*
* @param isRest 是否rest模式
* @return this
*/
public Request setRest(final boolean isRest) {
this.isRest = isRest;
return this;
}
/**
* 发送请求
*
@ -306,7 +345,30 @@ public class Request implements HeaderOperation<Request> {
* @param engine 自自定义引擎
* @return 响应内容
*/
public Response send(final ClientEngine engine){
public Response send(final ClientEngine engine) {
return engine.send(this);
}
/**
* 对于GET请求将参数加到URL中<br>
* 此处不对URL中的特殊字符做单独编码<br>
* 对于非rest的GET请求且处于重定向时参数丢弃
*/
private UrlBuilder urlWithParamIfGet() {
if (Method.GET.equals(method) && !this.isRest) {
final HttpBody body = this.body;
if (body instanceof FormBody) {
final UrlBuilder urlBuilder = UrlBuilder.of(this.url.toURL(), this.url.getCharset());
UrlQuery query = urlBuilder.getQuery();
if (null == query) {
query = UrlQuery.of();
urlBuilder.setQuery(query);
}
query.addAll(((FormBody<?>) body).form());
return urlBuilder;
}
}
return this.url();
}
}

View File

@ -55,6 +55,15 @@ public abstract class FormBody<T extends FormBody<T>> implements HttpBody {
this.charset = charset;
}
/**
* 获取表单内容
*
* @return 表单内容
*/
public Map<String, Object> form(){
return this.form;
}
/**
* 设置map类型表单数据
*

View File

@ -141,7 +141,7 @@ public class HttpClient4Engine implements ClientEngine {
* @return {@link HttpUriRequest}
*/
private static HttpUriRequest buildRequest(final Request message) {
final UrlBuilder url = message.url();
final UrlBuilder url = message.handledUrl();
Assert.notNull(url, "Request URL must be not null!");
final URI uri = url.toURI();
@ -153,7 +153,7 @@ public class HttpClient4Engine implements ClientEngine {
message.headers().forEach((k, v1) -> v1.forEach((v2) -> requestBuilder.addHeader(k, v2)));
// 填充自定义消息体
final HttpBody body = message.body();
final HttpBody body = message.handledBody();
if(null != body){
requestBuilder.setEntity(new HttpClient4BodyEntity(
// 用户自定义的内容类型

View File

@ -31,15 +31,14 @@ import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.message.BasicHeader;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.net.url.UrlBuilder;
import org.dromara.hutool.http.GlobalHeaders;
import org.dromara.hutool.http.HttpException;
import org.dromara.hutool.http.client.ClientConfig;
import org.dromara.hutool.http.client.engine.ClientEngine;
import org.dromara.hutool.http.client.Request;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.http.client.body.HttpBody;
import org.dromara.hutool.http.client.engine.ClientEngine;
import org.dromara.hutool.http.meta.HeaderName;
import org.dromara.hutool.http.proxy.HttpProxy;
import org.dromara.hutool.http.ssl.SSLInfo;
@ -143,7 +142,7 @@ public class HttpClient5Engine implements ClientEngine {
*/
@SuppressWarnings("ConstantConditions")
private static ClassicHttpRequest buildRequest(final Request message) {
final UrlBuilder url = message.url();
final UrlBuilder url = message.handledUrl();
Assert.notNull(url, "Request URL must be not null!");
final URI uri = url.toURI();
@ -153,7 +152,7 @@ public class HttpClient5Engine implements ClientEngine {
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
// 填充自定义消息体
final HttpBody body = message.body();
final HttpBody body = message.handledBody();
if(null != body){
request.setEntity(new HttpClient5BodyEntity(
// 用户自定义的内容类型

View File

@ -100,7 +100,7 @@ public class JdkClientEngine implements ClientEngine {
* @throws IOException IO异常
*/
private void doSend(final JdkHttpConnection conn, final Request message) throws IOException {
final HttpBody body = message.body();
final HttpBody body = message.handledBody();
if (null != body) {
// 带有消息体一律按照Rest方式发送
body.writeClose(conn.getOutputStream());
@ -121,7 +121,7 @@ public class JdkClientEngine implements ClientEngine {
final ClientConfig config = ObjUtil.defaultIfNull(this.config, ClientConfig::of);
final JdkHttpConnection conn = JdkHttpConnection
.of(message.url().toURL(), config.getProxy())
.of(message.handledUrl().toURL(), config.getProxy())
.setConnectTimeout(config.getConnectionTimeout())
.setReadTimeout(config.getReadTimeout())
.setMethod(message.method())//
@ -162,7 +162,7 @@ public class JdkClientEngine implements ClientEngine {
if (code != HttpURLConnection.HTTP_OK) {
if (HttpStatus.isRedirected(code)) {
message.url(getLocationUrl(message.url(), conn.header(HeaderName.LOCATION)));
message.url(getLocationUrl(message.handledUrl(), conn.header(HeaderName.LOCATION)));
if (redirectCount < message.maxRedirectCount()) {
redirectCount++;
return send(message, isAsync);

View File

@ -105,9 +105,9 @@ public class JdkHttpConnection implements HeaderOperation<JdkHttpConnection>, Cl
*/
public JdkHttpConnection setMethod(final Method method) {
if (Method.POST.equals(method) //
|| Method.PUT.equals(method)//
|| Method.PATCH.equals(method)//
|| Method.DELETE.equals(method)) {
|| Method.PUT.equals(method)//
|| Method.PATCH.equals(method)//
|| Method.DELETE.equals(method)) {
this.conn.setUseCaches(false);
// 增加PATCH方法支持
@ -364,7 +364,11 @@ public class JdkHttpConnection implements HeaderOperation<JdkHttpConnection>, Cl
// 修改为POST而且无法调用setRequestMethod方法修改因此此处使用反射强制修改字段属性值
// https://stackoverflow.com/questions/978061/http-get-with-request-body/983458
if (method == Method.GET && method != getMethod()) {
FieldUtil.setFieldValue(this.conn, "method", Method.GET.name());
try {
FieldUtil.setFieldValue(this.conn, "method", Method.GET.name());
} catch (final RuntimeException ignore) {
// JDK9+中可能反射失败忽略之
}
}
return out;
@ -410,7 +414,6 @@ public class JdkHttpConnection implements HeaderOperation<JdkHttpConnection>, Cl
/**
* 断开连接
*
*/
@Override
public void close() {

View File

@ -13,13 +13,12 @@
package org.dromara.hutool.http.client.engine.okhttp;
import okhttp3.OkHttpClient;
import okhttp3.internal.http.HttpMethod;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.http.client.ClientConfig;
import org.dromara.hutool.http.client.body.HttpBody;
import org.dromara.hutool.http.client.engine.ClientEngine;
import org.dromara.hutool.http.client.Request;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.http.client.body.HttpBody;
import org.dromara.hutool.http.client.engine.ClientEngine;
import org.dromara.hutool.http.proxy.HttpProxy;
import org.dromara.hutool.http.ssl.SSLInfo;
@ -126,14 +125,14 @@ public class OkHttpEngine implements ClientEngine {
*/
private static okhttp3.Request buildRequest(final Request message) {
final okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
.url(message.url().toURL());
.url(message.handledUrl().toURL());
// 填充方法
final String method = message.method().name();
final HttpBody body = message.body();
final HttpBody body = message.handledBody();
// if (HttpMethod.permitsRequestBody(method)) {
if (null != body) {
// 为了兼容支持rest请求在此不区分是否为GET等方法一律按照body是否有值填充
// 为了兼容支持rest请求在此不区分是否为GET等方法一律按照body是否有值填充兼容
builder.method(method, new OkHttpRequestBody(body));
} else {
builder.method(method, null);

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.http;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.map.MapBuilder;
import org.dromara.hutool.http.client.Request;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.http.client.engine.httpclient4.HttpClient4Engine;
import org.dromara.hutool.http.meta.Method;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
public class IssueI85C9STest {
@Test
void getWithFormTest() {
final Response send = Request.of("http://localhost:8888/formTest")
.method(Method.GET)
.form(MapBuilder.of(new HashMap<String, Object>()).put("a", 1).put("b", 2).build())
.send(new HttpClient4Engine());
Console.log(send.bodyStr());
}
}

View File

@ -27,56 +27,58 @@ public class SimpleServerTest {
public static void main(final String[] args) {
HttpUtil.createServer(8888)
.addFilter(((req, res, chain) -> {
Console.log("Filter: " + req.getPath());
chain.doFilter(req.getHttpExchange());
}))
// 设置默认根目录classpath/html
.setRoot(FileUtil.file("html"))
// get数据测试返回请求的PATH
.addAction("/get", (request, response) ->
response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString())
)
// 返回JSON数据测试
.addAction("/restTest", (request, response) -> {
final String res = JSONUtil.ofObj()
.set("id", 1)
.set("method", request.getMethod())
.set("request", request.getBody())
.toStringPretty();
response.write(res, ContentType.JSON.toString());
})
// 获取表单数据测试
// http://localhost:8888/formTest?a=1&a=2&b=3
.addAction("/formTest", (request, response) ->
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString())
)
.addFilter(((req, res, chain) -> {
Console.log("Filter: " + req.getPath());
chain.doFilter(req.getHttpExchange());
}))
// 设置默认根目录classpath/html
.setRoot(FileUtil.file("html"))
// get数据测试返回请求的PATH
.addAction("/get", (request, response) ->
response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString())
)
// 返回JSON数据测试
.addAction("/restTest", (request, response) -> {
final String res = JSONUtil.ofObj()
.set("id", 1)
.set("method", request.getMethod())
.set("request", request.getBody())
.toStringPretty();
response.write(res, ContentType.JSON.toString());
})
// 获取表单数据测试
// http://localhost:8888/formTest?a=1&a=2&b=3
.addAction("/formTest", (request, response) -> {
Console.log(request.getMethod());
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString());
}
)
// 文件上传测试
// http://localhost:8888/formForUpload.html
.addAction("/file", (request, response) -> {
Console.log("Upload file...");
Console.log(request.getParams());
final UploadFile[] files = request.getMultipart().getFiles("file");
// 传入目录默认读取HTTP头中的文件名然后创建文件
for (final UploadFile file : files) {
file.write("d:/test/");
Console.log("Write file: d:/test/" + file.getFileName());
}
response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString());
}
)
// 测试输出响应内容是否能正常返回Content-Length头信息
.addAction("test/zeroStr", (req, res)-> {
res.write("0");
Console.log("Write 0 OK");
}).addAction("/getCookie", ((request, response) -> {
response.setHeader(HeaderName.SET_COOKIE.toString(),
ListUtil.of(
new HttpCookie("cc", "123").toString(),
new HttpCookie("cc", "abc").toString()));
response.write("Cookie ok");
}))
.start();
// 文件上传测试
// http://localhost:8888/formForUpload.html
.addAction("/file", (request, response) -> {
Console.log("Upload file...");
Console.log(request.getParams());
final UploadFile[] files = request.getMultipart().getFiles("file");
// 传入目录默认读取HTTP头中的文件名然后创建文件
for (final UploadFile file : files) {
file.write("d:/test/");
Console.log("Write file: d:/test/" + file.getFileName());
}
response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString());
}
)
// 测试输出响应内容是否能正常返回Content-Length头信息
.addAction("test/zeroStr", (req, res) -> {
res.write("0");
Console.log("Write 0 OK");
}).addAction("/getCookie", ((request, response) -> {
response.setHeader(HeaderName.SET_COOKIE.toString(),
ListUtil.of(
new HttpCookie("cc", "123").toString(),
new HttpCookie("cc", "abc").toString()));
response.write("Cookie ok");
}))
.start();
}
}