fix redirect bug

This commit is contained in:
Looly 2024-12-03 00:35:23 +08:00
parent 9cc75d5e34
commit 4aa6f799b2
7 changed files with 164 additions and 68 deletions

View File

@ -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;
}
}

View File

@ -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>

View File

@ -27,6 +27,15 @@ public class RequestContext {
private Request request;
private int redirectCount;
/**
* 构造
*
* @param request 请求
*/
public RequestContext(final Request request) {
this.request = request;
}
/**
* 获取请求
*

View File

@ -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;
}
}

View File

@ -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);
}
/**
* 构建请求体
*

View File

@ -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

View File

@ -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());
}
}