URL 模式


一個請求 URI 實際上是由三個部份所組成,你可以使用 HttpServletRequestgetRequestURI() 來取得:

requestURI = contextPath + servletPath + pathInfo

其中 contextPath 是環境路徑(Context path),是容器用來決定該挑選哪個 Web 應用程式的依據(一個容器上可能部署多個Web應用程式),在 Servlet 4.0 之前,環境路徑的設定並沒有標準規範,而是依伺服器實作而有所不同。例如在 Tomcat 9 中,可以設定 context.xml 的 <Context path="xxxx"> 來決定,在 Servlet 4.0 中,可以於 web.xml 中使用 <default-context-path> 來設定預設路徑,然而,容器實作廠商可以覆蓋這個設定。

你可使用 HttpServletRequestgetContextPath() 來取得。如果應用程式環境路徑與 Web 伺服器環境根路徑相同,則應用程式環境路徑為空字串,如果不是,則應用程式環境路徑以 / 開頭,不包括 / 結尾。

一旦決定是哪個 Web 應用程式來處理請求,接下來就進行 Servlet 的挑選,Servlet 必須設定 URL 模式(URL pattern),可以設定的格式是:

  • / 開頭但 /* 結尾的 URL 模式被用於路徑對應(Path mapping)。例如若設定 URL 模式為 /guest/*,則請求 URI 扣去環境路徑的部份若為 /guest/test.view、/guest/home.view 等以 /guest/ 作為開頭的,都會交由該Servlet處理。
  • *. 開頭的 URL 模式被用於延伸對應(extension mapping)。例如若 URL 模式設定為 *.view,則所有以 .view 結尾的請求,都會交由該 Servlet 處理。
  • 空字串是個特殊的URL模式,對應至環境根目錄(Context root),也就是 / 的請求,但不用於設置 <url-pattern>urlPattern 屬性。例如若環境根目錄為App,則 http://host:port/App/ 的請求,路徑資訊是 /,而 Servlet 路徑與環境路徑都是空字串。
  • 僅包括 / 的 URL 模式,表示預設 Servlet,當找不到適合的 URL 模式對應時,就會使用預設 Servlet。
  • 其他的字串設定,都是用在於嚴格匹配(Exact match)。

如果 URL 模式在設定比對的規則在某些 URL 請求時有所重疊,例如若有 /admin/login.do/admin/**.do 三個URL模式設定,則請求時比對的原則是從最嚴格的 URL 模式開始符合。如果你請求 /admin/login.do,則一定是由 URL 模式設定為 /admin/login.do 的 Servlet 來處理,而不會是 /admin/**.do。如果你請求 /admin/setup.do,則是由 /admin/* 的 Servlet 來處理,而不會是 *.do

在最上面的 requestURI 中,servletPath 的部份是指 Servlet 路徑(Servlet path),不包括路徑資訊(Path info)與請求參數(Request parameter)。Servlet 路徑直接對應至URL模式資訊,可使用 HttpServletRequestgetServletPath() 來取得,Servlet 路徑基本上是以 / 開頭,但 /* 與空字串的URL模式比對而來的請求除外,在 /* 與空字串的情況下,Servlet 路徑是 ""

例如若某個請求是根據 /hello.do 對應至 Servlet,則 Servlet 路徑就是 "/hello.do",如果是透過 /servlet/* 對應至 Servlet,則 Servlet 路徑就是 "/servlet",但如果是透過 /* 或空字串,則 Servlet 路徑就是 ""

在最上面的 requestURI 中,pathInfo 的部份是指路徑資訊(Path info),路徑資訊不包括請求參數,指的是不包括 Context Path 與 Servlet Path 部份的額外路徑資訊。可使用 HttpServletRequestgetPathInfo() 來取得。如果沒有額外路徑資訊,則為 null(延伸對應、預設 Servlet、嚴格匹配的情況下),如果有額外路徑資訊,則是個以 "/" 為開頭的字串。

例如若對以下的 Servlet 請求之 URL 為 http://localhost:8080/ServletDemo/servlet/path:

package cc.openhome;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/servlet/*")
public class Path extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("    <head>");
        out.println("        <title>Path Servlet</title>");
        out.println("    </head>");
        out.println("    <body>");
        out.println(request.getRequestURI() + "<br>");
        out.println(request.getContextPath() + "<br>");
        out.println(request.getServletPath() + "<br>");
        out.println(request.getPathInfo());
        out.println("    </body>");
        out.println("</html>");
    }
}

則會顯示以下的內容:

<!DOCTYPE html>
<html>
    <head>
        <title>Path Servlet</title>
    </head>
    <body>
/ServletDemo/servlet/path<br>
/ServletDemo<br>
/servlet<br>
/path
    </body>
</html>

在 Servlet 4.0 中,HttpServletRequest 新增了 getHttpServletMapping() 方法,可以取得 javax.servlet.http.HttpServletMapping 實作物件,透過該物件,可以在執行時期,偵測執行中的 Servlet,是透過哪個 URL 對應而來,以及被比對到的值為何等,例如:

package cc.openhome;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/servlet/*")
public class Path extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        HttpServletMapping mapping = request.getHttpServletMapping();

        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("    <head>");
        out.println("        <title>Path Servlet</title>");
        out.println("    </head>");
        out.println("    <body>");
        out.println(mapping.getMappingMatch() + "<br>");
        out.println(mapping.getMatchValue() + "<br>");
        out.println(mapping.getPattern());
        out.println("    </body>");
        out.println("</html>");
    }
}

HttpServletMappinggetMappingMatch() 會傳回 javax.servlet.http.MappingMatch 列舉值,成員有 CONTEXT_ROOTDEFAULTEXACTEXTENSIONPATH,個別的 URL 模式意義在前面有談過了。

getMatchValue() 會傳回實際上符合的比對值,getPattern() 傳回使用之 URL 模式,例如,若請求 http://localhost:8080/ServletDemo/servlet/path,那麼會路徑比對成功,而比對值是 path,結果會顯示如下:

<!DOCTYPE html>
<html>
    <head>
        <title>Path Servlet</title>
    </head>
    <body>
PATH<br>
path<br>
/servlet/*
    </body>
</html>