Sự khởi tạo được kiểm soát bởi các tham số khởi tạo
Thông thường có ba nhóm đối tượng có nhu cầu điều chỉnh servlet hoặc JSP page.
(1) Developer: thay đổi hành vi của servlet bằng cách thay đổi code. (2) End user: thay đổi hành vi của servlet bằng cách nhập dữ liệu vào
HTML form.
(3) Deployer: chuyển servlet từ machine này sang machine khác và thay đổi một vài tham số nhất định (địa chỉ CSDL, thư mục chứa tập tin dữ liệu,…) mà không phải chỉnh sửa code của servlet. Đây chính là mục đích sử dụng chính của phương thức khởi tạo có tham số. Dưới đây là hướng dẫn vắn tắt về cách sử dụng hàm khởi tạo có tham số:
Bước 1: Sử dụng phần tử web.xml servlet để đặt tên cho servlet.
Bước 2: Sử dụng phần tử web.xml servlet-mapping để gán custom URL cho servlet (không sử dụng URL mặc định dạng http://.../servlet/ServletName khi sử dụng hàm init có tham số).
Bước 3: Thêm subelement init-paramvào phần tử web.xml servlet để gán tên và giá trị của các tham số khởi tạo.
Bước 4: Trong phương thức init của servlet, gọi phương getServletConfig để lấy tham chiếu đến đối tượng ServletConfig.
Bước 5: Gọi phương thức getInitParameter của ServletConfig theo tên của tham số khởi tạo. Kết quả trả về chính là giá trị của tham số init (có thể mang giá trị null nếu không tìm thấy tham số khởi tạo có tên giống tên tham số truyền vào).
5.6.4. Phương thức destroy
Server gọi phương thức destroy khi muốn loại bỏ thể hiện của servlet (servlet instance). Phương thức này thực hiện việc đóng CSDL của servlet, tạm dừng tất cả các thread mà servlet đã tạo, chép cookies lên đĩa cứng đồng thời xóa các activity.
5.7. SINGLETHREADMODEL INTERFACE
Thông thường hệ thống sẽ tạo một thể hiện của servlet (servlet instance), sau đó, với mỗi request sẽ tạo một thread mới. Trường hợp nếu có một request được gửi đến trong khi request trước đó vẫn chưa xử lý xong sẽ dẫn đến việc nhiều thread (multithread) cùng truy xuất tới một servlet. Khi đó, phương thức doGet và doPost sẽ phải thực hiện việc
đồng bộ hóa các truy xuất tới các trường và các dữ liệu chia sẻ khác bởi vì nhiều thread có thể truy xuất dữ liệu cùng lúc. Lưu ý rằng các biến cục bộ sẽ không được chia sẻ giữa các thread, do đó cần có cách bảo vệ đặc biệt.
Có thể ngăn ngừa multithread access bằng cách cài đặt SingleThreadModel interface.
Bảng 5.11 - Cài đặt SingleThreadModel interface
public class YourServlet extends HttpServlet
implements SingleThreadModel { ...
}
5.8. GỠ LỖI SERVLET
Phần bên dưới liệt kê một số cách gỡ lỗi servlet được sử dụng phổ biến.
(1) Sử dụng lệnh print: System.out.println
(2) Sử dụng các tiện ích gỡ lỗi được tích hợp sẵn trong IDE. (3) Sử dụng log file.
(4) Sử dụng Apache Log4J: một package thuộc Apache Jakarta Project. (5) Viết các class tách biệt nhau.
(6) Có kế hoạch cho các trường hợp thiếu dữ liệu hoặc dữ liệu bị thay đổi. (7) Kiểm tra HTML source code.
(8) Kiểm tra riêng từng dữ liệu request. (9) Kiểm tra riêng từng dữ liệu response. (10) Stop và restart lại server.
Chương 6
XỬ LÝ YÊU CẦU TỪ CLIENT – FORM DATA
Một trong những mục tiêu quan trọng của việc xây dựng web động là để kết quả sinh ra có thể dựa trên dữ liệu nhập vào từ user. Phần này sẽ trình bày cách làm thế nào để truy xuất vào dữ liệu nhập, đồng thời giải thích cách sử dụng giá trị mặc định khi bỏ quên có một vài tham số quan trọng, cách lọc dữ liệu (chẳng hạn như ký hiệu <, >) ra khỏi dữ liệu request để tránh làm rối các kết quả HTML, cách lấy tham số request bị mất, cách hiển thị lại các giá trị bị mất đã được đánh dấu.
6.1. VAI TRÒ CỦA FORM DATA
Xét một ví dụ địa chỉ URL sau đây: http://host/path?user=Marty+Hall&origin=bwi&dest=sfo
Trong URL này, phần chuỗi nằm phía sau dấu hỏi: user=Marty+Hall&origin=bwi&dest=sfo chính là form data (còn gọi là query data). Form data được xem là cách phổ biến nhất dùng để lấy thông tin từ một Web page gửi tới server. Form data có thể đặt ở cuối của URL, sau dấu hỏi nếu sử dụng GET request, hoặc cũng có thể được gửi tới server bằng một dòng riêng biệt nếu dùng POST request.
Sử dụng phần tử FORM (FORM element) để tạo HTML form. Dùng thuộc tính ACTION để chỉ định địa chỉ của servlet hoặc JSP page xử lý kết quả (có thể sử dụng địa chỉ tuyệt đối hoặc tương đối).
<FORM ACTION="...">...</FORM>
Nếu bỏ qua thuộc tính ACTION , dữ liệu sẽ được gửi tới trang hiện hành.
Sử dụng phần tử INPUT để gom user data. Đặt các phần tử này ở giữa thẻ đóng/mở của phần tử FORM, và cung cấp tên cho mỗi phần tử INPUT.
<INPUT TYPE="TEXT" NAME="..."> Đặt nút SUBMIT ở gần cuối form. <INPUT TYPE="SUBMIT">
Khi nhấn nút này, URL xác định bằng phần tử ELEMENT. Đối với GET request, một dấu hỏi và các cặp name/value sẽ được gắn vào
cuối URL, với giá trị name được cung cấp bởi thuộc tính NAME trong phần tử INPUT và value được nhập từ người dùng. Đối với POST request, cũng tương tự như trên nhưng khác ở chỗ dữ liệu sẽ được đặt trong một dòng tách biệt thay vì gắn vào URL.
6.2. ĐỌC FORM DATA TỪ SERVLET
6.2.1. Đọc giá trị của tham số (một giá trị): getParameter
Để đọc giá trị tham số của request, chỉ cần gọi phương thức getParameter của lớp HttpServletRequest. Phương thức này có phân biệt hoa thường, trả về giá trị kiểu chuỗi. Khi tham số tồn tại nhưng không có giá trị thì kết quả trả về sẽ là chuỗi rỗng (ví dụ như trong trường hợp người dùng quên nhập dữ liệu vào một textfield nào đó trên HTML form) cũng như trả về giá trị null khi không tồn tại tham số cần lấy.
request.getParameter("Param1")
6.2.2. Đọc giá trị của tham số (chứa nhiều giá trị): getParameterValues getParameterValues
Nếu một tham số xuất hiện nhiều lần trong form data, chúng ta nên gọi phương thức getParameterValues (trả về mảng giá trị kiểu chuỗi). Phương thức trả về giá trị null nếu tham số không tồn tại, trả về mảng một phần tử nếu tham số chỉ có một giá trị.
6.2.3. Tìm tham số theo tên: getParameterNames và getParameterMap getParameterMap
Phương thức getParameterNames trả về danh sách tên tham
số, kiểu Enumeration, với mỗi phần tử trong danh sách có thể
chuyển sang kiểu String, sau đó có thể dùng để truyền vào phương thức getParameter hoặc getParameterValues. Nếu request hiện tại không có tham số, phương thức getParameterNames sẽ trả về một Enumeration rỗng (khác null).
Lưu ý: Enumeration là một interface, có hỗ trợ phương thức hasMoreElements và nextElement. Thêm vào đó, do một số cấu trúc dữ liệu thông thường (hash table…) không có thứ tự giữa các phần tử, do đó không nên dựa vào việc phương thức getParameterNames sẽ trả về tên các tham số theo thứ tự (hiển thị dưới dạng HTML).
Tương tự, phương thức getParameterMap trả về một Map bao gồm một table keys (các chuỗi tên tham số) và một table values (giá trị của tham số, dạng chuỗi).
6.2.4. Đọc form data dạng thô và parse các file được upload: getReader hoặc getInputStream getReader hoặc getInputStream
Thay vì đọc từng tham số (form parameter), chúng ta có thể truy xuất vào truy vấn dữ liệu trực tiếp thông qua việc gọi phương thức
getReader hoặc getInputStream trong class
HttpServletRequest và sau đó, sử dụng dòng dữ liệu (stream) đó để phân tích (parse) dữ liệu đầu vào (dữ liệu thô – draw input). Tuy nhiên, nếu đọc dữ liệu theo cách này thì không đảm bảo được hỗ trợ phương thức getParameter.
Mặc dù vậy, việc đọc dữ liệu thô thực sự không phải ý kiến hay. Bởi vì dữ liệu nhập có thể hoặc là chưa được phân tích (cú pháp) hoặc sẽ xuất hiện một số ký tự liên quan đến mã ASCII. Chính vì vậy, phương pháp này chỉ nên dùng trong hai trường hợp sau đây:
(1) Dữ liệu gửi tới từ một custom client chứ không phải từ một HTML form. Custom client phổ biến nhất là Applet.
(2) Dữ liệu gửi từ một tập tin được upload (HTML có hỗ trợ phần tử INPUT TYPE="FILE"...> , cho phép client upload một tập tin nào đó lên server). Bởi vì servlet API không hỗ trợ việc đọc các file được upload thế này, nên chúng ta cần một thư viện bên ngoài (third-party library) để thực hiện việc này. Một trong những thư viện phổ biến nhất là Apache Jakarta Project.
6.3. XỬ LÝ SÓT DỮ LIỆU VÀ DỮ LIỆU BỊ THAY ĐỔI
Một câu hỏi được đặt ra là, trong trường hợp người dùng nhập sót dữ liệu cần thiết thì servlet sẽ làm gì? Có hai cách để giải quyết trường hợp này: sử dụng các giá trị mặc định hoặc hiển thị lại form để người dùng nhập lại thông tin còn thiếu.
6.3.1. Sử dụng giá trị mặc định
Khi xem xét các tham số request, cần kiểm tra ba điều kiện sau:
(1) Giá trị null: kiểm tra giá trị có null hay không trước khi tiến hành gọi bất kỳ phương thức nào trên chuỗi kết quả của phương thức getParameter.
(2) Giá trị là chuỗi rỗng: so sánh chuỗi với “” bằng phương thức
equals (so sánh chiều dài chuỗi với giá trị 0), không dùng toán
tử “==” để kiểm tra. Lưu ý, cần sử dụng phương thức trim trước khi so sánh.
String param = request.getParameter("someName");
if ((param == null) || (param.trim().equals(""))) { doSomethingForMissingValues(...);
} else {
doSomethingWithParameter(param); }
(3) Giá trị là chuỗi sai định dạng.
6.3.2. Hiển thị lại form
Trong một số trường hợp, phương pháp sử dụng giá trị mặc định lại không hiệu quả khi mà không tìm được giá trị mặc định thích hợp. Hai vấn đề được đặt ra như sau:
(1) Người dùng không cần nhập lại những giá trị đã nhập.
(2) Các thông tin còn thiếu sẽ được đánh dấu cho người dùng thấy. Dưới đây là một số phương pháp tiếp cận nhằm giải quyết hai vấn đề trên:
(1) Có một servlet hiển thị form, xử lý dữ liệu, hiển thị kết quả. Trước hết, servlet này chờ dữ liệu request được gửi đến, nếu không có, nó hiển thị form trống (blank form). Nếu servlet chỉ nhận được một phần thông tin, nó sẽ điền vào form và đánh dấu các ô chưa điền. Nếu nhận đủ thông tin được yêu cầu, servlet sẽ xử lý request và hiển thị kết quả.
(2) Có một servlet để hiển thị form, và thêm một servlet nữa để xử lý dữ liệu và hiển thị kết quả. Cách tiếp cận này có ưu điểm hơn cách (1) vì mỗi servlet sẽ nhỏ hơn và dễ quản lý hơn. Tuy nhiên nó đòi hỏi hai kỹ thuật: chuyển control từ servlet này sang servlet khác (phương thức response.sendRedirect hoặc phương thức
forward của class RequestDispatcher) và truy xuất
user-specific data được tạo ở một servlet khác (lưu trữ user- specific data trong đối tượng HttpSession).
(3) Có một trang JSP hiển thị form, một servlet hoặc một trang JSP khác xử lý dữ liệu và hiển thị kết quả. Đây là cách tốt hơn, được sử dụng rộng rãi. Nó đòi hỏi kiến thức về JSP cùng hai vấn đề đã nêu trong cách (2).
(4) Có một trang JSP hiển thị form, tự động điền vào các ô với các giá trị thu được từ data object và một trang JSP khác hoặc một servlet để xử lý dữ liệu và hiển thị kết quả. Đây là cách tốt nhất nhưng
ngoài vấn đề đã nêu ở cách (3) còn đòi hỏi thêm kỹ thuật điều chỉnh JSP tag.
6.3.3. Đọc input trong tập nhiều ký tự: setCharacterEncoding
Có hai cách thực hiện:
(1) Đọc tham số trong tập một ký tự (one character set) sau đó chuyển đổi nó sang dạng khác. Dùng phương thức getBytes để lấy các byte dữ liệu thô, sau đó đưa vào String constructor cùng với tên của ký tự mong muốn. String firstNameWrongEncoding = request.getParameter("firstName"); String firstName = new String(firstNameWrongEncoding.getBytes(), "Shift_JIS");
(2) Sử dụng tập ký tự được Java hỗ trợ tính năng autodetect và chuyển đổi từ tập ký tự mặc định.
request.setCharacterEncoding("JISAutoDetect");
String firstName = request.getParameter("firstName");
6.4. LỌC CÁC KÝ TỰ ĐẶC BIỆT
Trong hầu hết các trường hợp, rất dễ dàng để tìm ra các ký tự đặc biệt và thay thế theo các chuẩn của HTML. Tuy nhiên, có hai trường hợp không dễ thay thế:
(1) Chuỗi lấy từ một đoạn chương trình hoặc một nguồn nào đó được quy định sẵn bởi một định dạng chuẩn. Tìm từng ký tự và thay thế một cách thủ công là một cách giải quyết cho trường hợp này, tuy nhiên, nếu quên dù chỉ một ký tự cũng dẫn đến trường hợp trang web có một phần bị thiếu hoặc có định dạng không đúng.
(2) Chuỗi được lấy từ dữ liệu dạng HTML. Ở đây, việc chuyển đổi hoàn toàn phải được thực hiện trong thời gian chạy, vì chúng ta không biết các dữ liệu truy vấn lúc biên dịch. Nếu người dùng vô tình hoặc cố ý xâm nhập vào các thẻ HTML, trang web được tạo ra sẽ chứa các thẻ HTML khác nhau và có thể có kết quả hoàn toàn không thể đoán trước.
Cách tiếp cận để giải quyết bài toán này là sử dụng code. Việc thay thế các ký tự < , >, “, & trong chuỗi là một vấn đề đơn giản, có nhiều cách giải quyết. Tuy nhiên, cần lưu ý rằng chuỗi trong Java là
không thay đổi (không thể chỉnh sửa), vì vậy các tác động lên chuỗi (cộng chuỗi) liên quan đến việc tạo chuỗi copy:
String s1 = "Hello"; String s2 = s1 + " World";
Trong ví dụ trên, do không thể chỉnh sửa trực tiếp trên chuỗi s1, cho nên ta phải tạo một bản coppy của s1 rồi nối chuỗi “World” vào . Để tránh việc tạo quá nhiều copy (chiếm bộ nhớ quá nhiều), chúng ta nên sử dụng StringBuffer .
Ví dụ dưới đây trình bày phương thức filter, sử dụng StringBuffer để copy ký tự từ một chuỗi sang chuỗi kết quả (chuỗi đã lọc) một cách hiệu quả.
Bảng 6.1 - ServletUtility.java
package coreservlets;
import javax.servlet.*;
import javax.servlet.http.*;
public class ServletUtilities { ...
/** Thay các ký tự đặc. */
public static String filter(String input) { if (!hasSpecialChars(input)) {
return(input); }
StringBuffer filtered = new
StringBuffer(input.length()); char c;
for(int i=0; i<input.length(); i++) { c = input.charAt(i);
switch(c) {
case
'<':filtered.append("<"); break;
case
'>':filtered.append(">"); break;
case
'"':filtered.append("""); break;
case
'&':filtered.append("&"); break;
default: filtered.append(c); }
}
return(filtered.toString()); }
private static boolean hasSpecialChars(String input) {
boolean flag = false;
if ((input != null) && (input.length() > 0)) {
char c;
for(int i=0; i<input.length(); i++) { c = input.charAt(i);
switch(c) {
case '<': flag = true; break;
case '>': flag = true; break;
case '"': flag = true; break;
case '&': flag = true; break; } } } return(flag); } }
Chương 7
SỬ DỤNG JDBC VỚI SERVLET
Trong chương này, chúng ta sẽ tìm hiểu cách truy cập cơ sở dữ liệu quan hệ từ chương trình Java.
7.1. CÀI ĐẶT JDBC DRIVER
Việc tương tác với các cơ sở dữ liệu quan hệ (ta gọi tắt là database) thường được thực hiện thông qua một API chuẩn gọi là Java Database Connectivity (JDBC). Các nhà cung cấp database thường cung cấp bản cài đặt JDBC API có thể làm việc được trên database mà họ cung cấp. Đứng từ góc độ của chương trình Java sử dụng JDBC, ta sẽ không thấy sự khác biệt nào giữa các database, bởi vì mọi chi tiết về việc giao tiếp với database đều bị ẩn đi ở bên trong thư viện cài đặt JDBC. Trong chương này, ta giả sử đang làm việc với MySQL. Đối với các hệ quản trị cơ sở dữ liệu khác, chúng ta sẽ tiến hành cài đặt tương tự. Chúng ta thực hiện các bước sau để cài đặt JDBC.
Bước 1: Download gói MySQL Connector/J tại website của MySQL.
Bước 2: Giải nén tập tin download được ở bước 1, sau đó copy tập tin .jar vào thư mục Project → Properties → Java Build Path → Libraries → Add External Jars... (nếu làm việc với Eclipse). Một cách khác là copy tập tin này vào thư mục lib bên trong ${TOMCAT_HOME} (dùng ký hiệu này để chỉ biến lưu trữ thư mục chứa TOMCAT). Lý do của việc copy file .jar đến thư mục này thay vì copy vào WEB-INF/lib của project là để sau này ta có thể cấu hình Tomcat sao cho nó có thể tạo và quản lý một database connection pool cho phép tất cả các ứng dụng web được cài đặt với Tomcat có thể sử dụng được pool này. Mặt khác, chúng ta cũng không cần truy cập JDBC driver ở thời điểm dịch chương trình (build time), nên không cần phải copy nó vào build path của project (chương trình chỉ truy cập JDBC driver ở lúc chạy). Vì vậy, ta cần đặt JDBC driver vào trong runtime classpath của Tomcat. Tập tin .jar MySQL Connector/J chứa phần cài đặt của giao diện JDBC, và chi tiết cài đặt hoàn toàn không thấy được từ chương trình, mà chỉ có thể được tham chiếu trong lúc chương trình chạy.
Lứu ý rằng việc dùng database connection pool là một good practice. Bởi vì nó cho phép loại bỏ chi phí thiết lập lại các kết nối đã bị bỏ đi. Trong khi ứng dụng web thường xử lý rất nhiều request từ nhiều