在 Servlet/JSP 開發中,準確拼裝完整請求 URL 或生成資源絕對路徑 (CSS/JS/圖片) 是基礎技能。本文梳理 HttpServletRequest 核心 API、各路徑組成部分、典型應用場景與避坑指南。
getScheme() | http / https | 協議 |
| getServerName() | localhost / 192.168.1.100 | 主機名或 IP |
| getServerPort() | 8080 | 端口 (標準 80/443 會被容器隱藏) |
| getContextPath() | /myapp / "" (ROOT) | Web 應用上下文路徑 |
| getServletPath() | /user/login | Servlet 映射路徑 |
| getPathInfo() | /extra/path | 額外路徑 (通配符映射時) |
| getQueryString() | id=1&name=test | 查詢字串 (不含 ?) |
| getRequestURI() | /myapp/user/login | contextPath + servletPath + pathInfo |
| getRequestURL() | https://host:port/myapp/user/login | **完整 URL (不含查詢字串)** |
public static String getFullRequestUrl(HttpServletRequest request) {
StringBuilder url = new StringBuilder();
url.append(request.getScheme()).append("://")
.append(request.getServerName());
int port = request.getServerPort();
if (port != 80 && port != 443) { // 標準端口不顯示
url.append(":").append(port);
}
url.append(request.getRequestURI()); // 已包含 contextPath + servletPath + pathInfo
String query = request.getQueryString();
if (query != null) {
url.append("?").append(query);
}
return url.toString();
}
<%-- JSP 經典寫法 --%>
<%
String basePath = request.getScheme() + "://"
+ request.getServerName()
+ (request.getServerPort() == 80 ? "" : ":" + request.getServerPort())
+ request.getContextPath() + "/";
%>
<head>
<base href="<%=basePath%>">
<link rel="stylesheet" href="static/css/app.css">
<script src="static/js/app.js"></script>
</head>
contextPath | servletPath (請求 /myapp/user/login?id=1) | getRequestURI() |
|----------|------------------------|---------------------------------------------------------------|----------------------------|
| 非 ROOT (contextPath=/myapp) | /myapp | /user/login | /myapp/user/login |
| ROOT (contextPath=="") | "" | /myapp/user/login | /myapp/user/login |
> **注意**:ROOT 部署時,servletPath 會包含原本屬於 contextPath 的部分,**永遠用 getRequestURI() 取得統一路徑**,不要手動拼 contextPath + servletPath。
Nginx/Apache 反向代理後,getServerName() 可能回傳內網 IP,getServerPort() 可能是 8080 而非 443。需啟用 Forwarded / X-Forwarded-* 標頭並配置容器:
// Spring Boot application.yml
server:
forward-headers-strategy: framework # 或 native
tomcat:
remoteip:
remote-ip-header: X-Forwarded-For
protocol-header: X-Forwarded-Proto
port-header: X-Forwarded-Port
或手動修正:
String scheme = request.getHeader("X-Forwarded-Proto") != null ?
request.getHeader("X-Forwarded-Proto") : request.getScheme();
String serverName = request.getHeader("X-Forwarded-Host") != null ?
request.getHeader("X-Forwarded-Host") : request.getServerName();
int serverPort = request.getHeader("X-Forwarded-Port") != null ?
Integer.parseInt(request.getHeader("X-Forwarded-Port")) : request.getServerPort();
| 錯誤寫法 | 問題 | 正確做法 |
|---|---|---|
request.getRequestURL().toString() + "?" + request.getQueryString() | getRequestURL 回傳 StringBuffer,toString() 後再拼接效率低 | 用 StringBuilder 一次組裝,或直接用 request.getRequestURI() |
硬編碼 https://localhost:8080 | 部署環境變更即失效 | 動態取得 scheme/host/port/contextPath |
| 忽略標準端口 80/443 | URL 顯示 :80、:443 不美觀 | 判斷端口後決定是否拼接 |
在 Filter 中用 request.getRequestURI() 做權限匹配 | 未排除 contextPath 導致規則失效 | 用 request.getServletPath() 或手動移除 contextPath |
getRequestURL() + getQueryString() (或手動組裝更可控)。scheme://host:port/contextPath/,JSP 用 <base>,現代模板引擎用 @{/}。Forwarded Headers 策略,否則生成的 URL 會指向內網位址。WebUtils.getFullUrl(request) / WebUtils.getBasePath(request),避免分散重複代碼。