2026-07-14 21:13:57 +08:00
|
|
|
|
package com.ruoyi.common.utils;
|
|
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 请求公开地址工具。
|
|
|
|
|
|
*
|
|
|
|
|
|
* <p>应用位于反向代理后时,Servlet 请求看到的 serverName/serverPort 可能是代理的内网地址,
|
|
|
|
|
|
* 因此优先使用代理传递的公开地址,再回退到 Host 和 Servlet 请求信息。</p>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public final class RequestUrlUtils
|
|
|
|
|
|
{
|
2026-07-14 22:18:58 +08:00
|
|
|
|
/** 反向代理未透传公开地址时使用的默认访问地址(不带末尾斜杠,便于拼接路径)。 */
|
2026-07-14 23:07:43 +08:00
|
|
|
|
private static final String DEFAULT_PUBLIC_BASE_URL = "http://39.98.44.136:6024/shihezi";
|
2026-07-14 22:18:58 +08:00
|
|
|
|
|
2026-07-14 21:13:57 +08:00
|
|
|
|
private RequestUrlUtils()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static String buildBaseUrl(HttpServletRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (request == null) {
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String forwardedHeader = request.getHeader("Forwarded");
|
|
|
|
|
|
String forwarded = firstForwardedValue(forwardedHeader, "host");
|
|
|
|
|
|
String forwardedHost = firstHeaderValue(request.getHeader("X-Forwarded-Host"));
|
|
|
|
|
|
String hostHeader = request.getHeader("Host");
|
2026-07-14 22:09:58 +08:00
|
|
|
|
String requestHost = firstNonBlank(hostHeader, request.getServerName());
|
2026-07-14 22:18:58 +08:00
|
|
|
|
String forwardedPublicHost = firstNonBlank(forwarded, forwardedHost);
|
|
|
|
|
|
if (isInternalHost(requestHost) && isInternalHost(forwardedPublicHost)) {
|
|
|
|
|
|
String contextPath = request.getContextPath();
|
|
|
|
|
|
return DEFAULT_PUBLIC_BASE_URL + (contextPath == null ? "" : contextPath);
|
|
|
|
|
|
}
|
2026-07-14 22:09:58 +08:00
|
|
|
|
boolean hasPublicHost = !isBlank(forwarded) || !isBlank(forwardedHost)
|
2026-07-14 22:18:58 +08:00
|
|
|
|
|| !isBlank(hostHeader);
|
2026-07-14 21:13:57 +08:00
|
|
|
|
String host = firstNonBlank(
|
|
|
|
|
|
forwarded,
|
|
|
|
|
|
forwardedHost,
|
|
|
|
|
|
hostHeader,
|
|
|
|
|
|
request.getServerName());
|
|
|
|
|
|
host = normalizeHost(host);
|
|
|
|
|
|
|
|
|
|
|
|
String scheme = firstNonBlank(
|
|
|
|
|
|
firstForwardedValue(request.getHeader("Forwarded"), "proto"),
|
|
|
|
|
|
firstHeaderValue(request.getHeader("X-Forwarded-Proto")),
|
|
|
|
|
|
request.getScheme());
|
|
|
|
|
|
scheme = normalizeScheme(scheme, request.getScheme());
|
|
|
|
|
|
|
|
|
|
|
|
String forwardedPort = firstNonBlank(
|
|
|
|
|
|
extractPort(forwarded),
|
2026-07-14 22:18:58 +08:00
|
|
|
|
firstHeaderValue(request.getHeader("X-Forwarded-Port")));
|
2026-07-14 21:13:57 +08:00
|
|
|
|
if (!hasPort(host) && shouldIncludePort(forwardedPort, scheme)) {
|
|
|
|
|
|
host += ":" + forwardedPort;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!hasPort(host) && isBlank(forwardedPort) && !hasPublicHost
|
|
|
|
|
|
&& shouldIncludePort(String.valueOf(request.getServerPort()), scheme)) {
|
|
|
|
|
|
host += ":" + request.getServerPort();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String contextPath = request.getContextPath();
|
|
|
|
|
|
return scheme + "://" + host + (contextPath == null ? "" : contextPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-14 22:09:58 +08:00
|
|
|
|
private static boolean isInternalHost(String host)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(host)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
String normalized = host.trim().toLowerCase();
|
|
|
|
|
|
if (normalized.startsWith("[") && normalized.contains("]")) {
|
|
|
|
|
|
normalized = normalized.substring(1, normalized.indexOf(']'));
|
|
|
|
|
|
} else if (normalized.indexOf(':') == normalized.lastIndexOf(':')) {
|
|
|
|
|
|
normalized = normalized.substring(0, normalized.indexOf(':'));
|
|
|
|
|
|
}
|
|
|
|
|
|
return "localhost".equals(normalized)
|
|
|
|
|
|
|| "127.0.0.1".equals(normalized)
|
|
|
|
|
|
|| "0.0.0.0".equals(normalized)
|
|
|
|
|
|
|| "::1".equals(normalized);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-14 21:13:57 +08:00
|
|
|
|
private static String firstForwardedValue(String header, String name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(header)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
String firstElement = header.split(",", 2)[0];
|
|
|
|
|
|
String[] parameters = firstElement.split(";");
|
|
|
|
|
|
for (String parameter : parameters) {
|
|
|
|
|
|
String[] pair = parameter.split("=", 2);
|
|
|
|
|
|
if (pair.length == 2 && name.equalsIgnoreCase(pair[0].trim())) {
|
|
|
|
|
|
return stripQuotes(pair[1].trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String firstHeaderValue(String header)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(header)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return stripQuotes(header.split(",", 2)[0].trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String normalizeHost(String host)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(host)) {
|
|
|
|
|
|
return "localhost";
|
|
|
|
|
|
}
|
|
|
|
|
|
String normalized = stripQuotes(host.trim());
|
|
|
|
|
|
if (normalized.indexOf('\r') >= 0 || normalized.indexOf('\n') >= 0
|
|
|
|
|
|
|| normalized.indexOf('/') >= 0 || normalized.indexOf('?') >= 0
|
|
|
|
|
|
|| normalized.indexOf('#') >= 0) {
|
|
|
|
|
|
return "localhost";
|
|
|
|
|
|
}
|
|
|
|
|
|
return normalized;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String normalizeScheme(String scheme, String fallback)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ("https".equalsIgnoreCase(scheme)) {
|
|
|
|
|
|
return "https";
|
|
|
|
|
|
}
|
|
|
|
|
|
if ("http".equalsIgnoreCase(scheme)) {
|
|
|
|
|
|
return "http";
|
|
|
|
|
|
}
|
|
|
|
|
|
return "https".equalsIgnoreCase(fallback) ? "https" : "http";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String extractPort(String host)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(host)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
String normalized = host.trim();
|
|
|
|
|
|
if (normalized.startsWith("[") && normalized.contains("]:")) {
|
|
|
|
|
|
return normalized.substring(normalized.lastIndexOf(':') + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
int colon = normalized.lastIndexOf(':');
|
|
|
|
|
|
return colon > -1 && normalized.indexOf(':') == colon
|
|
|
|
|
|
? normalized.substring(colon + 1) : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static boolean hasPort(String host)
|
|
|
|
|
|
{
|
|
|
|
|
|
return !isBlank(extractPort(host));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static boolean shouldIncludePort(String port, String scheme)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isBlank(port)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
int portNumber = Integer.parseInt(port);
|
|
|
|
|
|
return portNumber > 0 && portNumber <= 65535
|
|
|
|
|
|
&& !(("http".equalsIgnoreCase(scheme) && portNumber == 80)
|
|
|
|
|
|
|| ("https".equalsIgnoreCase(scheme) && portNumber == 443));
|
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String firstNonBlank(String... values)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (String value : values) {
|
|
|
|
|
|
if (!isBlank(value)) {
|
|
|
|
|
|
return value.trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String stripQuotes(String value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value != null && value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
|
|
|
|
|
|
return value.substring(1, value.length() - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static boolean isBlank(String value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return value == null || value.trim().isEmpty();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|