請求參數、標頭


當請求來到伺服器時,Web 容器會建立 HttpServletRequest 實例來包裝請求中的相關訊息, HttpServletRequest 定義了取得一些通用請求資訊的方法,例如請求參數,可以使用以下的方法來取得:

  • getParameter()

    指定請求參數名稱來取得對應的值,例如:

    String username = request.getParameter("name");
    

    getParameter() 傳回的是 String 物件,若傳來的是像 "123" 這樣的字串值,而你需要的是基本資料型態,則必須使用 Integer.parseInt() 這類的方法將之剖析為基本型態。若請求中沒有所指定的請求參數名稱,則會傳回 null

  • getParameterValues()

    如果表單上有可複選的元件,例如核取方塊、多選清單等,則同一個請求參數名稱會有多個值(此時的 HTTP 查詢字串其實就是像 param=10&param=20&param=30), 此時你可以用 getParameterValues() 方法取得一個 String 陣列,陣列元素代表所有被選取選項的值。例如:

    String[] values = request.getParameterValues("param"); 
    
  • getParameterNames()

    如果想要知道請求中有多少請求參數,則可以使用 getParameterNames() 方法,這會傳回一個 Enumeration 物件,當中包括所有的請求參數名稱。例如:

    Enumeration<String> e = request.getParameterNames();
    while(e.hasMoreElements()) {
        String param = e.nextElement();
        String value = request.getParameter(param);
        out.println(value);
    }
    

    Enumeration 是從 JDK1.0 就存在的 API,如果你想使用增強式 for 迴圈,或者是 Java SE 8 新增 Stream API 的話,可以使用 java.util.Collections 的靜態方法 list(),將元素收集至 List 實例之中,例如:

    Collections.list(request.getParameterNames())
               .stream()
               .map(request::getParameter)
               .forEach(out::println);
    
  • getParameterMap()

    將請求參數以 Map 物件傳回,Map 中的鍵(Key)是請求參數名稱,值(Value)的部份是請求參數值,以字串陣列型態 String[] 傳回(考慮有同一請求參數有多個值的情況)。

對於 HTTP 的標頭(Header)資訊,可以使用以下的方法來取得:

  • getHeader()

    使用方式與 getParameter() 類似,指定標頭名稱後可傳回字串值,代表瀏覽器所送出的標頭訊息。

  • getHeaders()

    使用方式與 getParameterValues() 類似,指定標頭名稱後可傳回 Enumeration,元素為字串。

  • getHeaderNames()

    使用方式與 getParameterNames() 類似,取得所有標頭名稱,以 Enumeration 傳回,內含所有標頭字串名稱。

下面這個範例示範了如何取得並顯示瀏覽器送出的標頭訊息:

package cc.openhome;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/headers")
public class Headers extends HttpServlet {
    @Override
    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>Servlet ShowHeader</title>");
        out.println("</head>");
        out.println("<body>");

        Collections.list(request.getHeaderNames())
                   .forEach(name -> {
                       out.printf("%s: %s<br>%n", name, request.getHeader(name));
                   });

        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}

如果標頭值本身是個整數或日期的字串表示法,則可以使用 getIntHeader()getDateHeader() 方法分別取得轉換為 intDate 的值。如果 getIntHeader() 無法轉換為 int,則會丟出 NumberFormatException,如果 getDateHeader() 無法轉換為 Date,則會丟出 IllegalArgumentException

若請求參數是放在請求本體之中,例如 POST,而客戶端沒有在 Content-Type 標頭中設定字元編碼資訊(例如 Content-Type: text/html; charset=UTF-8),此時使用 HttpServletRequestgetCharacterEncoding() 傳回值會是 null,此時容器使用的預設編碼處理若是 ISO-8859-1(例如 Tomcat),而客戶端使用 UTF-8 發送非 ASCII 字元的請求參數,Servlet 取得的請求參數值,就會是不正確的結果(亂碼)。

HttpServletRequest 是有個 setCharacterEncoding() 方法,可以指定取得請求參數時所使用的編碼,這個方法必須在取得任何請求值之前執行:

request.setCharacterEncoding("UTF-8");

不過在規格書中對 setCharacterEncoding() 清楚提到其作用為:

Overrides the name of the character encoding used in the body of this request.

也就是說,基本上這個方法只對請求參數放在本體中時有用(例如 POST),就方才的程式片段來說,若瀏覽器以 UTF-8 來發送請求,取得的請求參數值就會是正確的結果。

從 Servlet 4.0 開始,也可以在 web.xml 中加入 <request-character-encoding>,設定整個應用程式要使用的請求參數編碼,如此一來,就不用特別在每次請求使用 HttpServletRequestsetCharacterEncoding() 方法來設定編碼了 例如:

<request-character-encoding>UTF-8</request-character-encoding>

當請求是用 GET 發送時,setCharacterEncoding() 沒有作用,究其原因,是因為處理 URI 的是 HTTP 伺服器,而非容器,對於 Tomcat 7 或先前版本附帶的 HTTP 伺服器來說,處理 URI 時使用的預設編碼是 ISO-8859-1,在不改變 Tomcat 附帶的 HTTP 伺服器 URI 編碼處理設定的情況下,常見使用底下的處理方式(若客戶端使用 UTF-8 發送請求):

String name = request.getParameter("name");
String name = new String(name.getBytes("ISO-8859-1"), "UTF-8");

從 Tomcat 8 之後,附帶的 HTTP 伺服器在 URI 編碼處理時預設使用 UTF-8 了,此時就不用如上自行轉換字串編碼了。

一旦開始學會從客戶端接受請求,取得請求參數或標頭等客戶端送出的資料之後,切記,永遠別相信你的客戶端是善良的,小心驗證、過濾請求參數或標頭等,以避免注入攻擊(Injection attack)之類的事情發生,永遠別把文件或書中簡單的範例程式直接拿來用,為了簡化範例,程式中往往不會考慮必要的驗證與過濾!