Giải pháp với Servlet
Servlet API được phát triển dựa trên những điểm mạnh của Java platform nhằm giải quyết vấn đề của CGI (Common Gateway Interface) và trình chủ server API. Nó là một API đơn giản hỗ trợ tất cả các Web server và thậm chí các ứng dụng máy chủ dùng để kiểm tra và quản lý các công việc trên server (load -balancing). Nó giải quyết vấn đề thực thi bằng việc thực hiện tất cả các yêu cầu như những thread trong một xử lý, hoặc trên một hệ thống load-balancing sẽ là mỗi xử lý trên một server trong kết chùm cluster. Các servlet dễ dàng chia sẽ tài nguyên.
Trong định nghĩa servlet, bảo mật được cải tiến theo nhiều cách. Trước hết, bạn hiếm khi thực thi lệnh trên shell với dữ liệu cung cấp bởi người dùng khi Java API đã cung cấp truy cập đến tất cả những hàm sử dụng thông dụng. Bạn có thể sử dụng JavaMail để đọc và gởi mail, Java DataBase Connect (JDBC) để truy cập các database, lớp File và những lớp quan hệ để truy cập hệ thống file, RMI, CORBA, Enterprise Java Beans (EJB) để truy cập các hệ thống kế thừa…
Mô hình bảo mật Java tạo cho nó khả năng bổ sung các điều khiển truy cập tốt hơn, cho thực thể chỉ cho phép truy cập đến một phần được xác định tốt hơn trong hệ thống file. Việc xử lý các ngoại lệ của Java cũng làm cho một servlet chắc
chắn hơn các trình chủ API C/C++ – một phép chia cho 0 được thông báo như một lỗi thay vì làm sụp đổ cả một hệ thống Web server.
Môi trường runtime của servlet
Một servlet là một lớp Java và vì thế cần được thực thi trên một máy ảo Java (JVM) bằng một dịch vụ gọi là servlet engine. Servlet engine tải lớp servlet lần đầu tiên servlet được yêu cầu, hoặc ngay khi servlet engine được bắt đầu. Servlet ngừng tải để xử lý nhiều yêu cầu khi servlet engine bị tắt hoặc nó bị dừng lại.
Một vài Web server như Java Web Server của Sun, JigSaw của W3C, hoặc LiteWebServer của Gefion có một servlet engine được xây dựng trên Java. Những Web server khác như Enterprise Server của Netscape, IIS của Microsoft hoặc Apache của Apache yêu cầu một module servlet engine add-on. Trình add-on này chuyển tất cả các yêu cầu cho các servlet, thực thi chúng và trả về trả lời cho trình duyệt thông qua Web server. Ví dụ các servlet engine add-on như WAICoolRunner của Gefion, WebSphere của IBM, Jrun của Live.
Tất cả các lớp servlet API và một Web server cho phép servlet đơn giản được tích hợp vời J2SDK có sẳn để download tại site servlet của Sun. Để bắt đầu với servlet, tôi yêu cầu bạn download J2SDK và chạy những ví dụ servlet đơn giản.
Các đối tượng request và response
Phương thức doGet có hai tham số là là httpRequestServlet và httpResponseServlet . Hai đối tượng này cho phép chúng ta truy cập đầy đủ tất cả các thông tin về yêu cầu và cho phép chúng ta gởi luồng xuất cho client như là trả lời cho yêu cầu.
Đối tượng HTTPServletRequest cũng cung cấp thông tin giống như biến môi trường của CGI theo một hướng chuẩn. Nó cũng cung cấp những phương thức để mở ra các tham số HTTP từ chuỗi truy vấn hoặc thân của yêu cầu phụ thuộc vào kiểu yêu cầu (GET hay POST). Là một nhà phát triển servlet, bạn truy cập các tham số theo cùng một cách cho cả hai kiểu yêu cầu. Những phương thức khác cho bạn truy cập đến tất cả những header của yêu cầu và giúp bạn phân tích các header ngày và cookie.
Thay vì viết trả lời như stdout giống như bạn làm với CGI, bạn nhận một đối tượng OutputStream hoặc PrintWriter từ HTTPServletResponse. OutputStream được dành cho dữ liệu nhị phân như các ảnh GIF hoặc JPEG, và PrintWriter dành cho việc xuất văn bản text. Bạn cũng có thể ấn định tất cả các header trả lời và mã trạng thái mà không phải phụ thuộc vào các cấu hình cụ thể của Web server CGI nào. Điều này làm cho servlet của bạn dễ dàng cài đặt.
Hãy bổ sung vào thân của phương thức doGet và xem chúng ta sử dụng những phương thức này như thế nào. Chúng ta có thể đọc hầu hết các thông tin chúng ta có được từ HTTPServletRequest (lưu những phương thức này vào ví dụ kế tiếp) và gởi những giá trị này để trả lời cho yêu cầu:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(“text/html”); PrintWriter out = response.getWriter(); // in ra HTML header out.println(“<HTML><HEAD><TITLE>”); out.println(“Request info”); out.println(“</TITLE></HEAD>”); // in ra than HTML out.println(“<BODY><H1>Request info</H1><PRE>”); out.println(“getCharacterEncoding: ” + request.getCharacterEncoding()); out.println(“getContentLength: ” + request.getContentLength()); out.println(“getContentType: ” + request.getContentType()); out.println(“getProtocol: ” + request.getProtocol()); out.println(“getRemoteAddr: ” + request.getRemoteAddr()); out.println(“getRemoteHost: ” + request.getRemoteHost()); out.println(“getScheme: ” + request.getScheme()); out.println(“getServerName: ” + request.getServerName()); out.println(“getServerPort: ” + request.getServerPort()); out.println(“getAuthType: ” + request.getAuthType()); out.println(“getMethod: ” + request.getMethod()); out.println(“getPathInfo: ” + request.getPathInfo()); out.println(“getPathTranslated: ” + request.getPathTranslated()); out.println(“getQueryString: ” + request.getQueryString()); out.println(“getRemoteUser: ” + request.getRemoteUser()); out.println(“getRequestURI: ” + request.getRequestURI()); out.println(“getServletPath: ” + request.getServletPath()); out.println(); out.println(“Parameters:”);
Enumeration paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement(); String[] values = request.getParameterValues(name); out.println(” ” + name + “:”);
for (int i = 0; i < values.length; i++) { out.println(” ” + values[i]);
} }
out.println();
out.println(“Request headers:”);
Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement(); String value = request.getHeader(name);
out.println(” ” + name + ” : ” + value); }
out.println();
out.println(“Cookies:”);
Cookie[] cookies = request.getCookies(); for (int i = 0; i < cookies.length; i++) { String name = cookies[i].getName(); String value = cookies[i].getValue(); out.println(” ” + name + ” : ” + value); }
// in ra footer HTML
out.println(“</PRE></BODY></HTML>”); out.close();
}
Phương thức doGet ở trên sử dụng hầu hết các phương thức trong HTTPServletRequest nhằm cung cấp thông tin về yêu cầu. Bạn có thể đọc chúng trong tài liệu servlet API, vì thế ở đây chúng ta chỉ tìm kiếm những gì đáng chú ý nhất.
getParameterNames và getParameterValues giúp bạn truy cập những tham
số HTTP nếu servlet được yêu cầu với phương thức GET hay post. getParameterValues trả về một dãy String bởi vì một tham số có thể có nhiều giá trị. Ví dụ nếu bạn yêu cầu servlet với một chuỗi URL
http://localhost:8080/servlet/ReqInfoServlet?foo=bar&foo=baz thì bạn sẽ thấy tham số foo có hai giá trị bar và baz. Điều này đúng nếu như bạn sử dụng chung một tên định danh cho nhiều thành phần của form HTML và sử dụng phương thức POST trong thẻ ACTION. Nếu như bạn chắc chắn rằng một tham số HTTP chỉ có thể có một giá trị thì bạn có thể sử dụng phương thức getParameter thay vì getParameterValues. Nó trả về một chuỗi String và nếu có nhiều giá trị nó sẽ trả về giá trị đầu tiên nhận được từ yêu cầu.
Bạn truy cập đến tất cả header của yêu cầu HTTP với các phương thức getHeaderNames và getHeader. getHeader trả về một chuỗi String của header. Nếu như bạn biết header có chứa giá trị ngày hay một giá trị integer bạn có thể nhận
được sự giúp đỡ chuyển đổi header sang một định dạng tương thích. getDateHeader trả về một ngày là số mili giây bắt đầu từ 00:00:00 GMT . ngày 1/1/1970. Đây là sự thay thế số chuẩn cho thời gian trong Java (tương tự như Unix). Bạn có thể sử dụng nó để khởi dựng một đối tượng ngày. getIntHeader trả về header giá trị kiểu int.
getCookie sẽ phân tích Cookie và trả về tất cả cookie trong một dãy các đối tượng Cookie. Để thêm một cookie vào trả lời, HTTPServletResponse cung cấp phương thức addCookie đặt một đối tượng Cookie như là một đối số.
Nếu như bạn biên dịch ReqInfoServlet và cài đặt nó trên servlet engine của bạn và gọi nó thông qua trình duyệt với một URL giống như và mọi thứ đều đúng theo sau: http://localhost:8080/servlet/ReqInfoServlet/foo/bar?fee=baz
thì trong trình duyệt sẽ hiển thị:
Request info getCharacterEncoding: getContentLength: -1 getContentType: null getProtocol: HTTP/1.0 getRemoteAddr: 127.0.0.1 getRemoteHost: localhost getScheme: http getServerName: thangnc getServerPort: 8080 getAuthType: null getMethod: GET getPathInfo: /foo/bar getPathTranslated: D:\PROGRA~1\jsdk2.1\httproot\servlet\ReqInfoServlet\foo\bar getQueryString: fee=baz getRemoteUser: null getRequestURI: /servlet/ReqInfoServlet/foo/bar getServletPath: /servlet/ReqInfoServlet Parameters: fee: baz Request headers: Connection : Keep-Alive
User-Agent : Mozilla/4.5 [en] (WinNT; I) Host : thangnc
Accept : image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding : gzip
Accept-Charset : iso-8859-1,*,utf-8
Cookie : TOMCATID=TO04695278486734222MC1010AT Cookies:
TOMCATID : TO04695278486734222MC1010AT
Nếu bạn muốn servlet này xử lý cả yêu cầu GET và POST? Việc bổ sung mặc định doGet và doPost trả về một thông điệp thông báo rằng phương thức này chưa được bổ sung. Vì thế chúng ta chỉ phải cung cấp một bổ sung mới của doGet. Để có thể xử lý một yêu cầu POST theo cùng một cách, chúng ta chỉ cần gọi doGet từ doPost.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response); }
Dữ liệu chia sẻ và cố định (Persistent and shared data)
Một trong những đặc điểm thú vị của Servlet API là hỗ trợ persistent data. Khi một servlet đã được tải theo các request và các servlet đã được tải vào trong cùng một xử lý, điều đó dễ dàng nhớ thông tin từ một request này đến một request khác và đến những servlet chia sẻ dữ liệu khác nhau.
Servlet API chứa một số các kỹ thuật để hỗ trợ điều này một cách trực tiếp. Chúng ta sẽ xem xét về chúng một cách chi tiết dưới đây. Một cách hữu hiệu khác xử lý dữ liệu được chia sẻ bằng cách sử dụng một đối tượng singleton.
Một lớp HttpSession đã được giới thiệu trong phiên bản 2.0 của Servlet API. Các thực thể của lớp này có thể nắm giữ thông tin về một session của người dùng giữa những yêu cầu khác nhau. Bạn bắt đầu một session mới bằng việc yêu cầu một đối tượng HttpSession từ lớp HttpServletRequest trong phương thức doGet hay doPost của bạn:
HttpSession session = request.getSession(true);
Phương thức này đặt vào một đối số kiểu boolean. Nếu đối số được đặt vào là true thì sẽ có một session mới được tạo ra nếu nó chưa tồn tại. Nếu đối số được đặt vào là false thì chỉ có một session tồn tại được trả về. Servlet API hỗ trợ hai cách để tập hợp nhiều request vào một session: cookie và ghi đè vào URL (URL rewriting).
Nếu cookie được sử dụng, một cookie với định danh session ID duy nhất được gởi đến client khi một session được tạo ra. Khi đó, client bao gồm cookie với tất cả request liên tục để servlet engine có thể tính toán session nào thì request được đi kèm. URL rewriting được thiết kế cho các client không hỗ trợ cookie hoặc khi người dùng không cho phép cookie.
Với URL rewriting, cookie sẽ được mã hóa trong các URL để servlet của bạn gởi đến cho client. Khi người dùng click vào một URL đã được mã hóa, session
ID sẽ được gởi đến server, nơi nó được giải mã và phải tập hợp request của client với session đúng. Để sử dụng URL rewriting, bạn phải chắc chắn rằng tất cả các session được gởi đến client phải được mã hóa với phương thức encodeURL hoặc encodeRedirectURL trong HttpServletResponse
Một HttpSession có thể lưu trữ bất kỳ một kiểu đối tượng nào. Một ví dụ thông dụng là một kết nối database cho phép nhiều request được đặt trong cùng một chuyển đổi database, hoặc là thông tin về sản phẩm đã được mua trong một ứng dụng Shopping cart cho phép người dùng có thể thêm vào một mặt hàng trong giỏ hàng của họ trong khi vẫn duyệt qua site. Để lưu một đối tượng trong một HttpSession, bạn dùng phương thức putValue
…
Connection con = driver.getConnection(databaseURL, user, password); session.putValue(“myApp.connection”, con);
…
Trong một servlet khác hoặc trong cùng một servlet nhưng xử lý một request khác, bạn có thể lấy đối tượng với phương thức getValue:
…
HttpSession session = request.getSession(true);
Connection con = (Connection) session.getValue(“myApp.connection”); if(con != null) {
// Thực thi những chuyển đổi dữ liệu …
}
Bạn có thể ngừng tương đối (làm mất hiệu lực) một session bằng một phương thức vô hiệu hóa hoặc để nó kết thúc (time-out) một cách tự động bằng servlet engine. Session kết thúc nếu không có một request nào đi kèm với session trong một khoảng thời gian xác định. Hầu hết các servlet engine đều cho phép bạn xác định độ dài của khoảng thời gian đó thông qua một lựa chọn cấu hình. Trong phiên bản 2.1 của Servlet API cũng kèm theo một phương thức setMaxInactiveInterval vì thế cho phép bạn chỉnh sửa thời gian để tương thích với các yêu cầu của mỗi ứng dụng cụ thể.
Các thuộc tính ServletContext
Tất cả các servlet thuộc về một ngữ cảnh (context) servlet. Trong phiên bản 1.0 và 2.0 của Servlet API, tất cả các servlet trên một host thuộc về một context, nhưng với phiên bản 2.1 của API, context trở nên hữu dụng hơn và có thể được xem như xuất phát điểm của một khái niệm Ứng dụng. Các phiên bản tương lai của API sẽ làm cho điều này thậm chí trở nên rõ ràng hơn.
Nhiều servlet engine bổ sung Servlet API 2.1 giúp cho bạn nhóm một tập các servlet vào trong một context và hỗ trợ nhiều context trong cùng một host. ServletContext trong API 2.1 có thể phụ thuộc vào trạng thái của các servlet của nó và sự phân biệt các tài nguyên (resource) và thuộc tính (attribute) có sẵn cho các servlet trong context. Ở đây, chúng ta sẽ chỉ xem xét các attribute của ServletContext có thể được dùng để chia sẻ thông tin giữa một nhóm các servlet như thế nào.
Có ba phương thức ServletContext tương ứng với các attribute: getAttribute, setAttribute và removeAttribute. Thêm vào đó, servlet engine có thể cung cấp nhiều cách để cấu hình servlet context với các giá trị attribute khởi tạo. Việc quản lý này giống như một sự chấp nhận thêm vào các đối số khởi tạo servlet cho việc cấu hình thông tin sử dụng bởi một nhóm các servlet, cho thực thể của định danh database mà chúng ta đã đề cập ở phần trước, một bảng kiểu (style sheet) URL cho một ứng dụng, tên của một mail server …
Một server nhận một tham chiếu đến đối tượng ServletContext của nó thông qua đối tượng ServletConfig. HttpServlet thật sự cung cấp một phương thức thuận lợi (thông qua siêu lớp của nó GenericServlet) mang tên getServletContext làm cho nó thật sự dễ dàng:
…
ServletContext context = getServletContext();
String styleSheet = context.getParameter(“stylesheet”); If(styleSheet != null) {
//Xác định một style sheet mới cho ứng dụng Context.setAttribute(“stylesheet”, styleSheet); }
…
Đoạn mã ở trên có thể là một phần của một ứng dụng cấu hình servlet, xử lý request từ một HTML FORM có một style sheet xác định cho ứng dụng. Tất cả các servlet trong ứng dụng tạo ra HTML có thể sử dụng attribute style sheet như sau:
…
ServletContext context = getServletContext();
String styleSheet = context.getAttribute(“stylesheet”); out.println(“<HTML><HEAD>”);
out.println(“<LINK HREF=” + styleSheet + “ TYPE=text/css REL=STYLESHEET>”);
…
Các thuộc tính và tài nguyên yêu cầu (Request attributes and resources)
Phiên bản 2.1 của API thêm vào hai kỹ thuật để chia sẽ dữ liệu giữa các servlet: request attibute và resource.
Các phương thức getAttribute, getAttributeNames và setAttribute được thêm vào cho lớp HttpServletRequest (hoặc chính xác hơn là cho siêu lớp ServletRequest). Chúng được xác định để sử dụng với RequestDispatcher, một đối tượng được sử dụng để chuyển tiếp (forward) một request từ một servlet này đến một servlet khác và để chứa một luồng xuất từ một servlet trong luồng xuất từ servlet chính.
Các phương thức getResourcev và getResourceAsStream của lớp ServletContext cho phép bạn truy cập đến những tài nguyên bên ngoài, chẳng hạn như một file cấu hình ứng dụng. Bạn có thể đã thân thuộc với những phương thức chia sẻ cùng một tên giống như những phương thức trong lớp ClassLoader. Tuy nhiên, những phương thức của lớp ServletContext có thể cung cấp cách truy cập đến những tài nguyên không thật sự là file. Một tài nguyên có thể được lưu trữ trong một database, có sẵn thông qua một LDAP server, trong trường hợp này tài nguyên là mọi thứ mà servlet engine quyết định hỗ trợ.
Servlet engine cung cấp một lựa chọn cấu hình context để bạn xác định được gốc của cơ sở tài nguyên, đó là một đường dẫn thư mục, một HTTP URL, một JDBC URL …
Đa tuyến (Multithreading)
Cách dễ dàng nhất để chắc chắn rằng đoạn mã an toàn là tránh kết thúc các biến thực thể (instance variable) và thay vào đó hãy đặt toàn bộ thông tin cần thiết vào một phương thức như những tham số.Ví dụ:
private String someParam;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
someParam = request.getParameter(“someParam”); processParam();
}
private void processParam() { // Xử lý với someParam
}
là không an toàn. Nếu phương thức doGet thực thi hai phương thức. Điều này có nghĩa là giá trị của instance variable someParam sẽ được đặt vào thread thứ hai trong khi thread đầu tiên vẫn còn sử dụng nó.
Chuyển sang một thread an toàn như sau:
private String someParam;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processParam(someParam); }
private void processParam(String someParam) { //Xử lý với someParam
}
Ở đây, phương thức processParam nhận vào tất cả dữ liệu nó cần như những tham số thay vì phải phụ thuộc vào các instance variable.