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.MapUtil;
import org.dromara.hutool.core.map.multi.ListValueMap; import org.dromara.hutool.core.map.multi.ListValueMap;
import org.dromara.hutool.core.net.url.UrlBuilder; 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.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.http.GlobalHeaders; import org.dromara.hutool.http.GlobalHeaders;
import org.dromara.hutool.http.HttpGlobalConfig; 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.HttpBody;
import org.dromara.hutool.http.client.body.StringBody; import org.dromara.hutool.http.client.body.StringBody;
import org.dromara.hutool.http.client.body.UrlEncodedFormBody; import org.dromara.hutool.http.client.body.UrlEncodedFormBody;
@ -105,6 +107,10 @@ public class Request implements HeaderOperation<Request> {
* 最大重定向次数 * 最大重定向次数
*/ */
private int maxRedirectCount; private int maxRedirectCount;
/**
* 是否是REST请求模式REST模式运行GET请求附带body
*/
private boolean isRest;
/** /**
* 默认构造 * 默认构造
@ -148,6 +154,15 @@ public class Request implements HeaderOperation<Request> {
return url; return url;
} }
/**
* 获取处理后的请求URL即如果为非REST的GET请求将form类型的body拼接为URL的一部分
*
* @return URL
*/
public UrlBuilder handledUrl() {
return urlWithParamIfGet();
}
/** /**
* 设置URL * 设置URL
* *
@ -227,12 +242,24 @@ public class Request implements HeaderOperation<Request> {
/** /**
* 获取请求体 * 获取请求体
* *
* @return this * @return 请求体
*/ */
public HttpBody body() { public HttpBody body() {
return this.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; 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 自自定义引擎 * @param engine 自自定义引擎
* @return 响应内容 * @return 响应内容
*/ */
public Response send(final ClientEngine engine){ public Response send(final ClientEngine engine) {
return engine.send(this); 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; this.charset = charset;
} }
/**
* 获取表单内容
*
* @return 表单内容
*/
public Map<String, Object> form(){
return this.form;
}
/** /**
* 设置map类型表单数据 * 设置map类型表单数据
* *

View File

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

View File

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

View File

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

View File

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

@ -48,8 +48,10 @@ public class SimpleServerTest {
}) })
// 获取表单数据测试 // 获取表单数据测试
// http://localhost:8888/formTest?a=1&a=2&b=3 // http://localhost:8888/formTest?a=1&a=2&b=3
.addAction("/formTest", (request, response) -> .addAction("/formTest", (request, response) -> {
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()) Console.log(request.getMethod());
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString());
}
) )
// 文件上传测试 // 文件上传测试
@ -67,7 +69,7 @@ public class SimpleServerTest {
} }
) )
// 测试输出响应内容是否能正常返回Content-Length头信息 // 测试输出响应内容是否能正常返回Content-Length头信息
.addAction("test/zeroStr", (req, res)-> { .addAction("test/zeroStr", (req, res) -> {
res.write("0"); res.write("0");
Console.log("Write 0 OK"); Console.log("Write 0 OK");
}).addAction("/getCookie", ((request, response) -> { }).addAction("/getCookie", ((request, response) -> {