mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix redirect bug
This commit is contained in:
parent
9cc75d5e34
commit
4aa6f799b2
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.http;
|
||||
|
||||
import org.dromara.hutool.core.net.url.UrlBuilder;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HttpUrlUtil {
|
||||
/**
|
||||
* 获取转发的新的URL
|
||||
*
|
||||
* @param parentUrl 上级请求的URL
|
||||
* @param location 获取的Location
|
||||
* @return 新的URL
|
||||
*/
|
||||
public static UrlBuilder getLocationUrl(final UrlBuilder parentUrl, String location) {
|
||||
final UrlBuilder redirectUrl;
|
||||
if (!HttpUtil.isHttp(location) && !HttpUtil.isHttps(location)) {
|
||||
// issue#I5TPSY
|
||||
// location可能为相对路径
|
||||
if (!location.startsWith("/")) {
|
||||
location = StrUtil.addSuffixIfNot(parentUrl.getPathStr(), "/") + location;
|
||||
}
|
||||
|
||||
// issue#3265, 相对路径中可能存在参数,单独处理参数
|
||||
final String query;
|
||||
final List<String> split = SplitUtil.split(location, "?", 2, true, true);
|
||||
if (split.size() == 2) {
|
||||
// 存在参数
|
||||
location = split.get(0);
|
||||
query = split.get(1);
|
||||
} else {
|
||||
query = null;
|
||||
}
|
||||
|
||||
redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(),
|
||||
location, query, null, parentUrl.getCharset());
|
||||
} else {
|
||||
// location已经是编码过的URL
|
||||
redirectUrl = UrlBuilder.ofHttpWithoutEncode(location);
|
||||
}
|
||||
|
||||
return redirectUrl;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.http.GlobalHeaders;
|
||||
import org.dromara.hutool.http.HttpGlobalConfig;
|
||||
import org.dromara.hutool.http.HttpUrlUtil;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.body.*;
|
||||
import org.dromara.hutool.http.client.engine.ClientEngine;
|
||||
@ -206,6 +207,16 @@ public class Request implements HeaderOperation<Request> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置重定向后的URL,用于处理相对路径
|
||||
*
|
||||
* @param location 重定向后的URL
|
||||
* @return this
|
||||
*/
|
||||
public Request locationTo(final String location){
|
||||
return url(HttpUrlUtil.getLocationUrl(handledUrl(), location));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义编码,一般用于:
|
||||
* <ul>
|
||||
|
@ -27,6 +27,15 @@ public class RequestContext {
|
||||
private Request request;
|
||||
private int redirectCount;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param request 请求
|
||||
*/
|
||||
public RequestContext(final Request request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求
|
||||
*
|
||||
|
@ -18,13 +18,9 @@ package org.dromara.hutool.http.client.engine.jdk;
|
||||
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.net.url.UrlBuilder;
|
||||
import org.dromara.hutool.core.net.url.UrlUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.HttpException;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.ClientConfig;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.RequestContext;
|
||||
@ -36,10 +32,8 @@ import org.dromara.hutool.http.meta.Method;
|
||||
import org.dromara.hutool.http.proxy.ProxyInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于JDK的UrlConnection的Http客户端引擎实现
|
||||
@ -70,7 +64,7 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
|
||||
@Override
|
||||
public JdkHttpResponse send(final Request message) {
|
||||
return doSend(new RequestContext().setRequest(message));
|
||||
return doSend(new RequestContext(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -194,21 +188,19 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
|
||||
if (code != HttpURLConnection.HTTP_OK) {
|
||||
if (HttpStatus.isRedirected(code)) {
|
||||
message.url(getLocationUrl(message.handledUrl(), conn.header(HeaderName.LOCATION)));
|
||||
if (HttpStatus.isRedirected(code)) {
|
||||
message.locationTo(conn.header(HeaderName.LOCATION));
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
|
||||
// 307方法和消息主体都不发生变化。
|
||||
if (HttpStatus.HTTP_TEMP_REDIRECT != code) {
|
||||
// 重定向默认使用GET
|
||||
message.method(Method.GET);
|
||||
}
|
||||
// https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
|
||||
// 307方法和消息主体都不发生变化。
|
||||
if (HttpStatus.HTTP_TEMP_REDIRECT != code) {
|
||||
// 重定向默认使用GET
|
||||
message.method(Method.GET);
|
||||
}
|
||||
|
||||
if (context.canRedirect()) {
|
||||
return doSend(context);
|
||||
}
|
||||
if (context.canRedirect()) {
|
||||
return doSend(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,41 +208,4 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
// 最终页面
|
||||
return new JdkHttpResponse(conn, this.cookieManager, context.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转发的新的URL
|
||||
*
|
||||
* @param parentUrl 上级请求的URL
|
||||
* @param location 获取的Location
|
||||
* @return 新的URL
|
||||
*/
|
||||
private static UrlBuilder getLocationUrl(final UrlBuilder parentUrl, String location) {
|
||||
final UrlBuilder redirectUrl;
|
||||
if (!HttpUtil.isHttp(location) && !HttpUtil.isHttps(location)) {
|
||||
// issue#I5TPSY
|
||||
// location可能为相对路径
|
||||
if (!location.startsWith("/")) {
|
||||
location = StrUtil.addSuffixIfNot(parentUrl.getPathStr(), "/") + location;
|
||||
}
|
||||
|
||||
// issue#3265, 相对路径中可能存在参数,单独处理参数
|
||||
final String query;
|
||||
final List<String> split = SplitUtil.split(location, "?", 2, true, true);
|
||||
if (split.size() == 2) {
|
||||
// 存在参数
|
||||
location = split.get(0);
|
||||
query = split.get(1);
|
||||
} else {
|
||||
query = null;
|
||||
}
|
||||
|
||||
redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(),
|
||||
location, query, null, parentUrl.getCharset());
|
||||
} else {
|
||||
// location已经是编码过的URL
|
||||
redirectUrl = UrlBuilder.ofHttpWithoutEncode(location);
|
||||
}
|
||||
|
||||
return redirectUrl;
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,14 @@ import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.client.ClientConfig;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.RequestContext;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.dromara.hutool.http.client.body.HttpBody;
|
||||
import org.dromara.hutool.http.client.cookie.InMemoryCookieStore;
|
||||
import org.dromara.hutool.http.client.engine.AbstractClientEngine;
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
import org.dromara.hutool.http.meta.HttpStatus;
|
||||
import org.dromara.hutool.http.meta.Method;
|
||||
import org.dromara.hutool.http.proxy.ProxyInfo;
|
||||
import org.dromara.hutool.http.ssl.SSLInfo;
|
||||
|
||||
@ -65,15 +69,7 @@ public class OkHttpEngine extends AbstractClientEngine {
|
||||
@Override
|
||||
public Response send(final Request message) {
|
||||
initEngine();
|
||||
|
||||
final okhttp3.Response response;
|
||||
try {
|
||||
response = client.newCall(buildRequest(message)).execute();
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return new OkHttpResponse(response, message);
|
||||
return doSend(new RequestContext(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,6 +141,43 @@ public class OkHttpEngine extends AbstractClientEngine {
|
||||
this.client = builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*
|
||||
* @param context 请求上下文
|
||||
* @return {@link Response}
|
||||
*/
|
||||
private Response doSend(final RequestContext context) {
|
||||
final Request message = context.getRequest();
|
||||
final okhttp3.Response response;
|
||||
try {
|
||||
response = client.newCall(buildRequest(message)).execute();
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
// 自定义重定向
|
||||
if(message.maxRedirects() > 0){
|
||||
final int code = response.code();
|
||||
if (HttpStatus.isRedirected(code)) {
|
||||
message.locationTo(response.header(HeaderName.LOCATION.getValue()));
|
||||
}
|
||||
// https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
|
||||
// 307方法和消息主体都不发生变化。
|
||||
if (HttpStatus.HTTP_TEMP_REDIRECT != code) {
|
||||
// 重定向默认使用GET
|
||||
message.method(Method.GET);
|
||||
}
|
||||
|
||||
if (context.canRedirect()) {
|
||||
return doSend(context);
|
||||
}
|
||||
}
|
||||
|
||||
return new OkHttpResponse(response, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求体
|
||||
*
|
||||
|
@ -360,6 +360,9 @@ public interface HttpStatus {
|
||||
* @since 5.6.3
|
||||
*/
|
||||
static boolean isRedirected(final int responseCode) {
|
||||
if(responseCode < 300){
|
||||
return false;
|
||||
}
|
||||
return responseCode == HTTP_MOVED_PERM
|
||||
|| responseCode == HTTP_MOVED_TEMP
|
||||
|| responseCode == HTTP_SEE_OTHER
|
||||
|
@ -26,7 +26,6 @@ import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -203,7 +202,19 @@ public class DownloadTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void downloadTeamViewerTest() throws IOException {
|
||||
public void downloadTeamViewerByOkHttpTest() {
|
||||
// 此URL有3次重定向, 需要请求4次
|
||||
final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe";
|
||||
HttpGlobalConfig.setMaxRedirects(2);
|
||||
|
||||
final Response send = Request.of(url).send(ClientEngineFactory.createEngine("okhttp"));
|
||||
Console.log(send.getStatus());
|
||||
Console.log(send.headers());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void downloadTeamViewerByJdkTest() {
|
||||
// 此URL有3次重定向, 需要请求4次
|
||||
final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe";
|
||||
HttpGlobalConfig.setMaxRedirects(2);
|
||||
@ -212,4 +223,16 @@ public class DownloadTest {
|
||||
Console.log(send.getStatus());
|
||||
Console.log(send.headers());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void downloadTeamViewerByHttpClient5Test() {
|
||||
// 此URL有3次重定向, 需要请求4次
|
||||
final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe";
|
||||
HttpGlobalConfig.setMaxRedirects(2);
|
||||
|
||||
final Response send = Request.of(url).send(ClientEngineFactory.createEngine("HttpClient5"));
|
||||
Console.log(send.getStatus());
|
||||
Console.log(send.headers());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user