Files
shz-backend/ruoyi-common/src/main/java/com/ruoyi/common/utils/RequestUrlUtils.java

233 lines
7.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.ruoyi.common.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
/**
* 请求公开地址工具。
*
* <p>应用位于反向代理后时Servlet 请求看到的 serverName/serverPort 可能是代理的内网地址,
* 因此优先使用代理传递的公开地址,再回退到 Host 和 Servlet 请求信息。</p>
*/
public final class RequestUrlUtils
{
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");
String requestHost = firstNonBlank(hostHeader, request.getServerName());
URI browserOrigin = isInternalHost(requestHost)
? firstBrowserOrigin(request)
: null;
String browserHost = browserOrigin == null ? null : getAuthority(browserOrigin);
boolean hasPublicHost = !isBlank(forwarded) || !isBlank(forwardedHost)
|| !isBlank(hostHeader) || browserOrigin != null;
String host = firstNonBlank(
forwarded,
forwardedHost,
browserHost,
hostHeader,
request.getServerName());
host = normalizeHost(host);
String scheme = firstNonBlank(
firstForwardedValue(request.getHeader("Forwarded"), "proto"),
firstHeaderValue(request.getHeader("X-Forwarded-Proto")),
browserOrigin == null ? null : browserOrigin.getScheme(),
request.getScheme());
scheme = normalizeScheme(scheme, request.getScheme());
String forwardedPort = firstNonBlank(
extractPort(forwarded),
firstHeaderValue(request.getHeader("X-Forwarded-Port")),
browserOrigin == null ? null : String.valueOf(browserOrigin.getPort()));
if ("-1".equals(forwardedPort)) {
forwardedPort = null;
}
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);
}
/**
* 反向代理未透传 Host 时Servlet 可能只能看到 127.0.0.1:9091。
* 对浏览器请求Origin/Referer 仍然包含用户实际访问的公开地址,作为兜底来源。
*/
private static URI firstBrowserOrigin(HttpServletRequest request)
{
URI origin = parseHttpUri(firstHeaderValue(request.getHeader("Origin")));
return origin != null
? origin
: parseHttpUri(firstHeaderValue(request.getHeader("Referer")));
}
private static URI parseHttpUri(String value)
{
if (isBlank(value)) {
return null;
}
try {
URI uri = new URI(value.trim());
String scheme = uri.getScheme();
return uri.getHost() != null && ("http".equalsIgnoreCase(scheme)
|| "https".equalsIgnoreCase(scheme))
? uri : null;
} catch (URISyntaxException e) {
return null;
}
}
private static String getAuthority(URI uri)
{
String host = uri.getHost();
if (host == null) {
return null;
}
if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
host = "[" + host + "]";
}
return uri.getPort() > 0 ? host + ":" + uri.getPort() : host;
}
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);
}
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();
}
}