เกริ่นนำ
แม้ว่าโลกของ Internet จะเพิ่งเกิดขึ้นเพียงไม่กี่ปีก็ตาม
เทคโนโลยีที่ใช้กับ Internet กลับมีการเปลี่ยนแปลงไปอย่างรวดเร็วมาก จำได้ว่าสมัยแรก
ๆ เพจต่าง ๆ ที่อยู่ในเวปจะเป็นลักษณะของ static page หรือเพจที่ไม่มีการเปลี่ยนแปลงเนื้อหาที่ไม่ว่าจะนานเท่าไหร่นอกเสียจากว่าผู้ดูแลเพจนั้นจะทำการอัพเดทเพจดังกล่าว
เพจลักษณะนี้เป็นเพจที่นิยมใช้กันทั่วไปใน Internet สมัยแรกเพราะอินเทอร์เนทยังนิยมกันอยู่ในวงแคบ
โดยกลุ่มผู้ใช้จะเป็นกลุ่มบุคคลที่อยู่ในวงการศึกษาเท่านั้น ต่อมาจากนั้นไม่นานทางผู้ผลิต
Browser ได้ทำการเพิ่มความสามารถให้กับเพจโดยอนุญาติให้เพจสามารถแทรก Script
เล็ก ๆ ลงไปรวมกับส่วนที่เป็น HTML ได้ซึ่งจุดนี้ก็คือจุดเริ่มต้นของ Client
Side JavaScript นั่นเอง
แม้ว่าเพจจะเริ่มมีความสามารถในการโต้ตอบกับผู้ใช้โดยอิงความสามารถจาก
JavaScript แล้วก็ตาม ถ้าพูดถึงในแง่ของส่วนเนื้อหาของตัวเพจจริง ๆ แล้วตัวเพจเองก็ยังคงเป็น
static page อยู่เช่นเดิม เมื่อกลุ่มผู้ใช้อินเทอร์เนทเริ่มมีมากขึ้นความต้องการที่จะให้เพจสามารถทำการรับส่งข้อมูลรวมไปถึงเปลี่ยนแปลงเนื้อหาได้โดยอัตโนมัติก็เกิดขึ้น เทคโนโลยีที่เกิดขึ้นเพื่อรองรับความต้องการเหล่านี้ก็คือ Server Side Application
นั่นเอง
Server Side Application ในระยะแรก ๆ มักถูกเขียนขึ้นด้วยคอนเซ็ปของ
CGI (Common Gateway Interface) โดยหลักการทำงานง่าย ๆ ก็คือ Web Browser
จะทำการส่งเดต้าที่เกิดจาก Action ของ User เช่น การคลิกลิงค์หรือการกรอกแบบสอบถามไปยัง Web Server โดยแทนที่ Web Server จะทำการส่งเพจที่เป็น static
page กลับมา Web Server จะทำการ forward เดต้าดังกล่าวไปยังโปรแกรมซึ่งถูกจัดไว้ โปรแกรมดังกล่าวจะทำการประมวลผลเดต้าที่ได้แล้วจะส่งผลกลับไปยัง Web Server
ซึ่งทาง Web Server ก็จะส่งผลที่ได้นี้กลับไปยัง Web Browser อีกทีหนึ่ง
หลาย ๆ คนคงเคยเห็น Server Side Application ที่เป็นผลิตผลของ
CGI ในรุ่นแรก ๆ ที่ยังหลงเหลืออยู่ในปัจจุบันนี้ ยกตัวอย่างเช่น Counter,
Image Map, Guessbook, SendMail เป็นต้น จริง ๆ แล้วตัว CGI เองสามารถเขียนด้วยภาษาอะไรก็ได้
แต่ที่นิยมมากที่สุดเห็นจะเป็น C และ Perl อาจจะเป็นเพราะว่า CGI เป็นส่ิงที่มีมากับ
Internet ตั้งแต่ช่วงแรก ๆ ดังนั้นไม่ว่าจะเป็น Web Server ไหนก็ตาม
Server Side Applcation พื้นฐานที่ทาง Web Server เหล่านั้นจะต้องสนันสนุนก็คือ CGI
ซึ่งจุดนี้เองที่เป็นจุดเด่นทำให้ CGI เป็นที่นิยมใช้กันอย่างกว้างขวางจนกระทั่งปัจจุบันนี้
What is Servlet?
Servlet เป็น Server Side Application แบบหนึ่งซึ่งอ้างอิงคอนเซ็ปมาจาก
CGI ข้อดีของ Servlet ที่อยู่เหนือ CGI อย่างแรกก็คือตัวภาษาที่ใช้เขียนซึ่งก็คือจาว่านั่นเอง จาว่าเป็นภาษาที่ใช้คอนเซ็ปของ Object Oriented ในการเขียน หลายคนที่เกี่ยวข้องกับการเขียนโปรแกรมสำหรับโปรเจคใหญ่
ๆ จะทราบดีว่า Object Oriented สามารถลดความซับซ้อนของโครงสร้างโปรแกรมรวมไปถึงการอำนวยความสะดวกในการ
reuse ส่วนของโปรแกรมที่เขียนไว้แล้วเพียงไร นอกจากนี้จาว่ายังเป็นภาษาที่เป็นลักษณะแบบ
platform independent ซึ่งจะช่วยให้เราสามารถที่จะทำการพัฒนาระบบโดยใช้ Environment
อะไรก็ได้ซึ่งโดยทั่วไปมักนิยมใช้ Window Environment โดยจะนำโปรแกรมที่เขียนเสร็จแล้วมารันบน
Unix Environment เพื่อเพิ่มความเสถียรภาพของโปรแกรมแทน
นอกจากนี้ Servlet ยังมีความเร็วที่สูงกว่า CGI
เพราะ Servlet ใช้หลักการของ thread โดยจะทำการสร้าง 1 thread ต่อหนึ่ง request
ที่มาจาก client ซึ่งในทางกลับกัน CGI จะทำการสร้าง 1 process ต่อหนึ่ง request*
ซึ่งจะทำให้เปลืองทรัพยาการมากกว่าและ process ในการรันก็จะช้ากว่าด้วย
ท้ายที่สุดจุดเด่นที่สำคัญของ Servlet ก็คือ API (Application Programming
Interface) โดยระบบที่ทำการพัฒนาโดยใช้คอนเซ็ปของ Servlet จะสามารถเรียกใช้
API ที่ทางจาว่ามีมาให้ (javax.servlet.*, javax.servlet.http.*) ซึ่งจะช่วยทำให้การพัฒนาระบบดังกล่าวง่ายและเร็วยิ่งขึ้น
Servlet Engine
ในการรันระบบ*ที่เขียนขึ้นโดยใช้หลักการของ Servlet
เราจะต้องนำระบบดังกล่าวมาบรรจุอยู่ในสิ่ง ๆ หนึ่งที่เรียกว่า Servlet Engine
ให้นึกถึง Servlet Engine คล้าย ๆ กับกล่อง ๆ หนึ่งที่ใส่ลูกปิงปองไว้หลายลูก
โดยลูกปิงปองแต่ละลูกก็คือระบบ ๆ หนึ่งนั่นเอง หลายคนอาจสงสัยทำไมถึงใช้คำว่าระบบ
โดยทั่วไป Server Side Application หนึ่ง ๆ ที่ถูกเขียนขึ้นโดยใช้ Servlet
API จะถูกเรียกว่า Servlet ในหนึ่งระบบอาจประกอบด้วย Servlet หลายอัน
ยกตัวอย่างเช่น ระบบที่เกี่ยวกับ Shopping Cart อาจจะประกอบด้วย Servlet
ที่ทำหน้าที่ในการเช็คล๊อกอิน, Servlet ที่ทำหน้าที่ในการเก็บข้อมูลสินค้า,
Servlet ที่ทำหน้าที่ในการส่งเมล์กลับไปยังลูกค้าเพื่อบอกว่าได้ทำการส่งของไปให้แล้ว
เป็นต้น ดังนั้นถ้ามองโดยรวมแล้ว Servlet Engine ก็คือที่รวมของระบบตั้งแต่หนึ่งระบบถึงหลายระบบ
โดยแต่ละระบบจะประกอบด้วย Servlet หนึ่งอันหรือมากกว่า ดังรูปที่ 1.
* ระบบ ในที่นี้อาจจะหมายถึง Zone (Apache
JServ) หรือ Web Application (Tomcat) ก็ได้
รูปที่ 1. Servlet Engine and its Servlets
Servlet Engine เป็นเพียงกล่อง ๆ หนึ่งที่ใช้บรรจุและรันกลุ่มของ Servlet เท่านั้น
ในการที่จะทำการติดต่อสื่อสารกับ Client ตัว Servlet Engine นี้จะต้องทำงานร่วมกัน
Web Server ซึ่งเปรียบเสมือนฉากหน้าที่ติดต่อกับ Client อีกทีหนึ่ง เมื่อใดก็ตามที่มี
request ส่งมาจาก Client ถ้า request นั้นเจาะจงมาที่ตัว Servlet ทาง Web
Server ก็จะทำการ forward ตัว request นั้นมาให้ Servlet Engine ซึ่งทาง Servlet
Engine ก็จะทำการเรียก Servlet ที่ Client ต้องการขึ้นมาทำการประมวลผล request นั้น
โดยท้ายสุด Servlet จะส่งผลกลับไปให้ Servlet Engine, Servlet Engine ก็จะ
forward ผลที่ได้กลับไปให้ Web Server ซึ่ง Web Server ก็จะส่งผลกลับไปให้
Client
Sevlet Engine อาจจะเป็นส่วนที่ติดมากับ Web Server อยู่แล้วยกตัวอย่างเช่น Servlet
Engine ที่อยู่ใน Netscape Enterprise Server, IBM WebSphere หรืออาจจะเป็นส่วนที่เป็น
Add-on ให้กับ Web Server ก็ได้เช่น Apache Jserv, Tomcat, JRun หรือแม้กระทั่งเป็นส่วนหนึ่งที่อยู่ใน
Application Server เช่น BEA Weblogic เป็นต้น ทั้งนี้การเลือกใช้ Servlet Engine
แต่ละชนิดก็มักขึ้นอยู่กับปัจจัยหลายอย่างเช่น ความสะดวกในการรวมระบบที่จะสร้างขึ้นมาใหม่กับระบบที่มีอยู่แล้ว,
งบประมาณที่มีอยู่สำหรับโครงการหรืออาจจะรวมไปถึงทักษะและประสบการณ์ส่วนตัวของนักพัฒนาแต่ละคน
How the Servlet Engine works? (สำหรับผู้ที่ไม่สนใจในหลักการทำงานของ
Servlet Engine อาจข้ามส่วนนี้ไปก็ได้)
สมมุติว่าเรามี interface อันหนึ่งชื่อ Servlet โดย interface นี้ประกอบไปด้วยฟังก์ชันสองฟังก์ชันดัง code snippet ข้างล่างนี้
interface Servlet {
public void init();
public void service();
}
ถ้าเราต้องการสร้างคลาส ๆ หนึ่งชื่อ MyServlet โดยคลาสดังกล่าว
implement Servlet interface คลาส MyServlet อาจจะเป็นอย่างข้างล่างนี้
class MyServlet implements Servlet {
public void init() {
System.out.println("Calling MyServlet init()...");
}
public void service() {
System.out.println("I'm MyServlet");
}
}
เราจะสังเกตเห็นว่าคลาส MyServlet ทำการ implement ฟังก์ชั่น init()
และ service() ซึ่งเป็นฟังก์ชันที่ถูก define อยู่ใน Servlet
interface
Servlet Engine ใช้ประโยชน์จากคอนเซ็ปของ interface ในการโหลด Servlet
ต่าง ๆ ในช่วง Runtime โดย Servlet ต่าง ๆ จะถูกโหลดและ cast กลับไปเป็นอยู่ในรูปของ
interface แทน
ถ้าดูจากตัวอย่างของเราหลังจาก MyServlet ถูก cast กลับไปเป็น
instance ของ Servlet interface แล้วทาง Servlet Engine ก็จะสามารถเรียกฟังก์ชันอะไรก็ได้ที่ถูก
define อยู่ใน Servlet interface (ในที่นี้ก็คือ init() และ service())
ตัวอย่าง code ง่าย ๆ ที่ Servlet Engine ใช้ในการโหลด Servlet ในช่วง
Runtime อาจจะเป็นอย่างข้างล่างนี้
public class SimpleServletEngine {
public static void main(String args[]) throws Exception {
if (args.length <= 0) {
System.out.println("Usage: java SimpleServletEngine
javaClassName");
System.exit(0);
}
System.err.println("loading class " + args[0] + "...");
Class cls = Class.forName(args[0]);
Servlet servlet = (Servlet) cls.newInstance(); //
casting
servlet.init();
servlet.service();
}
}
ตัว Servlet ที่ Servlet Engine โหลดจะถูกอ้างอิงมาจาก argument ที่ชื่อ
javaClassName โดย arguement นี้จะถูกส่งผ่านไปให้ Class.forName()
เพื่อเรียก Class object ที่จะใช้สร้าง Servlet instance ขึ้นมาโดยใช้คำสั่ง
Class.newInstance() ซึ่งหลังจากนั้น Servlet instance ที่ได้จะถูก
cast กลับให้กลายเป็น instance ของ Servlet interface เพื่อ Servlet Engine
จะได้ใช้ในการเรียกฟังก์ชั่น init() และ service()
เราอาจพูดง่าย ๆ ว่าหลักการทำงานของ Servlet Engine ก็คือการโหลดคลาสใดคลาสหนึ่งขึ้นมาโดยคลาสนั้นจะต้อง
implement ตัว interface ที่ชื่อ Servlet ซึ่งทาง Servlet Engine
ทราบอยู่แล้วว่ามีฟังก์ชันอะไรประกอบอยู่ใน Servlet interface บ้างโดยถ้า
Service Engine ทำการ cast คลาสที่่ implement Servlet interface
(ในที่นี้ก็คือ MyServlet) กลับไปเป็น instace ของ Servlet
interface แล้วตัว Servlet Engine ก็จะสามารถเรียกฟังก์ชันที่ชื่อ init()
และ service() ที่ define อยู่ใน Servlet interface จากคลาสดังกล่าวได้
์์Note: จริง ๆ แล้วถึงเราไม่ทำการ cast คลาส MyServlet ให้กลายเป็น instance ของ Servlet interface เราก็สามารถเรียกฟังก์ชัน
int() และ service() ได้ เพียงแต่จะเป็นการเรียกฟังก์ชันเหล่านี้ในนามของคลาส MyServlet ไม่ใช่ในนามของ Servlet interface
ในกรณีที่เราไม่ต้องการ implement ฟังก์ชันทุกฟังก์ชันที่ถูก define อยู่ใน Servlet
interface เราก็อาจจะสร้างคลาสที่เป็น abstract ขึ้นมาคลาสหนึ่งก่อนโดยคลาสนี้จะทำการ
implement ฟังก์ชันบางฟังก์ชันที่อยู่ใน Servlet interface แล้วให้
subclass ของ abstract คลาสนี้ทำการ implement ฟังก์ชันที่เหลือ ยกตัวอย่างเช่น
abstract class HttpServlet implements Servlet {
public void init() {
System.out.println("Calling HttpServlet init()...");
}
// not implemented yet
public abstract void service();
}
ถ้าเราต้องการสร้าง Servlet ขึ้นมาโดยจะ implement แค่ฟังก์ชัน service()
อย่างเดียว เราก็เพียง subclass คลาส HttpServlet เท่านั้นโดยคลาส
Servlet ของเราอาจจะเป็นดังตัวอย่างข้างล่างนี้
class AnotherServlet extends HttpServlet {
public void service() {
System.out.println("I'm AnotherServlet");
}
}
เราจะสังเกตเห็นว่าคลาส AnotherServlet จะไม่ต้องทำการ implement
ฟังก์ชัน init() เพราะตัว parent คลาสของมันซึ่งก็คือ HttpServlet
ได้ทำการ implement ไปแล้ว
ในการโหลดคลาส AnotherServlet เข้าไปยัง Servlet Engine ก็ยังใช้หลักการเดียวกันกับคลาส
MyServlet
ข้างต้นคือโหลดคลาส AnotherServlet แล้ว cast กลับให้กลายเป็น instance ของ Servlet
interface ซึ่งผลจากการรันโปรแกรม SimpleServletEngine โดยโหลด Servlet
(หรือคลาส)ที่ชื่อ MyServlet และ AnotherServlet ก็จะออกมาเป็นดังรูป
รูปที่ 2. การโหลด Servlet ต่าง ๆ ของ SimpleServletEngine
เพื่อเพิ่มความเข้าใจมากขึ้น ผู้อ่านอาจจะลองนำ SimpleServletEngine.java
ไปรันดูก็ได้
Note: ในการใช้งานจริงเมื่อไรก็ตามที่เราต้องการติดตั้ง Servlet
เข้าไปยัง Servlet Engine เราจะต้องทำการใส่รายละเอียดต่าง ๆ ของ Servlet
เช่น ชื่อคลาส, parameters ต่าง ๆ ลงไปยัง properties file ของ Servlet Engine
(อาจอยู่ในรูปของ xxx.properties ไฟล์หรือ xxx.xml ไฟล์) เพื่อที่จะบอก Servlet Engine ให้รับรู้และโหลด
Servlet ของเราได้อย่างถูกต้อง
Interface javax.servlet.Servlet
ถ้าใครอ่านหลักการทำงานของ Servlet Engine จากหัวข้อที่แล้วก็อาจจะเริ่มเข้าใจแล้วว่า
Servlet ทำงานอย่างไร (สำหรับคนที่ข้ามมาอ่านหัวข้อนี้เลยก็ไม่ผิดกติกาแต่อย่างใด)
หัวใจของ Servlet จริง ๆ อยู่ที่ interface ที่ชื่อ javax.servlet.Servlet*
โดยทุก Servlet ที่ถูกเขียนขึ้นจะต้องทำการ implement ตัว interface
นี้ไม่ทางตรงก็ทางอ้อม (ทางตรงก็คือการ implement ตัว interface นี้เลย ส่วนทางอ้อมก็คือการให้ Servlet ทำการ subclass
คลาสบางคลาสที่ได้ทำการ implement ตัว interface นี้ไว้แล้ว) เหตุผลว่าทำไมเราต้อง
implement ตัว interface นี้เพราะว่าเมื่อไรก็ตามที่มี request จาก client
เข้ามายัง Servlet Engine ตัว Servlet Engine จะทำการหา Servlet ที่ request
ดังกล่าวอ้างถึง หลังจากนั้น Servlet Engine จะทำการเรียกฟังก์ชันต่าง ๆ ที่อยู่ใน
Servlet เพื่อทำการประมวลผล request ของ client โดยฟังก์ชันที่
Servlet Engine จะทำการเรียกก็คือฟังก์ชันที่ Servlet ได้ทำการ implement
ซึ่งเป็นฟังก์ชันที่ถูก define อยู่ใน javax.servlet.Servlet interface
นั่นเอง หลายคนอาจจะถามว่าทำไม Servlet Engine
ถึงเรียกฟังก์ชันที่ถูก define อยู่ใน interface นี้ เหตุผลง่าย ๆ ก็คือ Servlet
เป็นส่วนที่ถูกโหลดเข้าไปใน Servlet Engine ในช่วง Runtime ตัว Servlet Engine
เองไม่สามารถทราบได้ว่า Servlet ต่าง ๆ มีฟังก์ชันอะไรประกอบอยู่บ้างนอกเสียจากว่า
Servlet นั้นได้ทำการ implement ฟังก์ชันที่เป็นมาตรฐานที่ Servlet Engine
รับรู้ซึ่งนี่ก็คือเหตุผลว่าทำไมทุก Servlet จะต้องทำการ implement ตัว javax.servlet.Servlet
interface
อย่างไรก็ตามเราสามารถให้ Servlet Engine เรียกฟังก์ชันอื่น ๆ ที่อยู่ใน
Servlet ได้ซึ่งวิธีการก็คือการใส่ฟังก์ชันดังกล่าวเข้าไปในส่วน implementation
ของฟังก์ชันต่าง ๆ ที่ถูก define อยู่ใน javax.servletServlet interface นั่นเอง
* สำหรับคนที่ยังใหม่กับจาว่าอาจจะสงสัยว่า javax คืออะไร? javax คือ
standard extention package ของ Sun ที่อยู่นอกเหนือจาก core API ที่อยู่ใน
JDK ตัว Servlet ที่เราพูดถึงกันอยู่นี้ก็เป็นหนึ่งใน standard extension
ของ Sun เช่นเดียวกัน ดังนั้น API ทุกอย่างที่เกี่ยวกับ Servlet ก็จะเป็น
javax.servlet.XXX.YYY.ZZZ
ดังนั้นหลักการง่าย ๆ ในการสร้าง Servlet ก็คือการสร้างคลาสขึ้นมาคลาสหนึ่งโดยคลาสนั้นจะต้องทำการ
implement ตัว interface ที่ชื่อ javax.servlet.Servlet เพียงเท่านี้เราก็ได้
Servlet เป็นของตัวเองแล้ว อย่างที่กล่าวมาข้างต้นว่าในการเขียน Servlet
เราอาจจะทำการ implement ตัว javax.servlet.Servlet interface ไม่ทางตรงก็ทางอ้อม
เพื่อที่จะอำนวยความสะดวกให้กับนักพัฒนา ทางจาว่าจึงได้มีการสร้างคลาสพื้นฐานที่ได้ทำการ
implement ตัว javax.servlet.Servlet interface ขึ้นมาสองคลาสคือคลาส
javax.servlet.GenericServlet และคลาส javax.servlet.http.HttpServlet (ซึ่งเป็น subclass ของ javax.servlet.GenericServlet อีกทีหนึ่ง)
ดังนั้นสิ่งที่นักพัฒนาจะต้องทำก็คือการ subclass คลาสใดคลาสหนึ่งในสองคลาสนี้แล้ว
override ฟังก์ชั่นที่ต้องการซึ่งโดยทั่วไปก็คือฟังก์ชั่นที่ชื่อ service() นั่นเอง
ถ้าเราดูความสัมพันธ์ระหว่าง javax.servlet.Servlet (interface), javax.servlet.GenericServlet (abstract
class) และ javax.servlet.http.HttpServlet (abstract class) เราจะเห็นว่าเริ่มแรก javax.servlet.Servlet จะถูก define ด้วย 5 ฟังก์ชันพื้นฐานที่ทุก
Servlet จะต้องทำการ implement ดังลิสของ API ที่ปรากฎอยู่ข้างล่างนี้
public interface Servlet
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws
IOException, ServletException;
public String getServletInfo();
public void destroy();
หลังจากนั้นคลาส javax.servlet.GenericServlet จะทำการ implement
ฟังก์ชั่นต่าง ๆ ที่อยู่ใน javax.servlet.Servlet ดังตัวหนาที่ปรากฎอยู่ข้างล่าง
ซึ่งนอกจากนี้คลาส GenericServlet ยังเพิ่มเติมฟังก์ชันเสริมอีกส่วนหนึ่งเพื่อช่วยให้นักพัฒนาทำการพัฒนา
Servlet ได้อย่างรวดเร็วและมีประสิทธิภาพมากขึ้น
public abstract class GenericServlet implements Servlet
public GenericServlet();
public String getInitParameter();
public Enumeration getInitParameterNames();
public ServletConfig getServletConfig();
public ServletContext getServletContext();
public String getServletInfo();
public void init();
public void init(ServletConfig config) throws ServletException;
public void log(String message);
public void log(String message, Throwable cause);
public abstract void service(ServletRequest req, ServletResponse
res) throws ServletException, IOException;
public void destroy();
เราจะสังเกตว่าคลาส GenericServlet เป็น abstract คลาสเพราะฟังก์ชัน
public void service(ServletRequest req,...) ของ javax.servlet.Servlet
interface จะไม่ถูก implement โดยคลาสนี้ ท้ายที่สุดคลาส GenericServlet จะถูก subclass อีกทีหนึ่งโดยคลาส
HttpServlet ซึ่งเป็นคลาสที่ถูกสร้างขึ้นเพื่อรองรับ Servlet ที่ถูกเขียนขึ้นสำหรับใช้ในการติดต่อสื่อสารกับ
Client ที่ใช้ Http โปรโตคอล ดังนั้นในการเขียน Servlet ของเรา ๆ ก็เพียงทำการ
subclass คลาส HttpServlet แล้วทำการ override ฟังก์ชันที่เราต้องการเท่านั้น
public abstract class HttpServlet extends GenericServlet
implements Serializable
public HttpServlet();
protected void doGet(HttpServletRequest req, HttpServletResponse
res) throws ServletException, IOException;
protected void doPost(HttpServletRequest req,HttpServletResponse
res) throws ServletException, IOException;
protected void doPut(HttpServletRequest req,HttpServletResponse
res) throws ServletException, IOException;
protected void doDelete(HttpServletRequest req, HttpServletResponse
res) throws ServletException, IOException;
protected void doOptions(HttpServletRequest req, HttpServletResponse
res) throws ServletException, IOException;
protected void doTrace(HttpServletRequest req, HttpServletResponse
res) throws ServletException, IOException;
protected void service(HttpServletRequest req, HttpServletResponse
res) throws ServletException, IOException;
public void service(ServletRequest req, ServletResponse res)throws
ServletException, IOException;
protected long getLastModifed(HttpServletRequest req);
Note: คลาส HttpServlet ถูกกำหนดให้เป็น abstract คลาสไม่ใช่เพราะว่ามีบางฟังก์ชันในตัวมันที่ไม่ได้มีการ
implement แต่เพราะว่าผู้ที่เขียนคลาสนี้ขึ้นมาไม่ต้องการให้เราทำการเรียกใช้คลาสนี้โดยตรงแต่ต้องการให้เราสร้าง
Servlet ขึ้นมาจากการ subclass คลาสนี้อีกทีหนึ่ง
Hello World
ในการเรียนรู้ภาษาอะไรก็ตาม โปรแกรมแรกที่เรามักจะเขียนก็คือโปรแกรมที่พิมพ์คำว่า
Hello World ออกมาดังนั้นในการเขียน Servlet เราก็จะทำเช่นเดียวกัน โปรแกรม Hello World ที่เขียนแบบ Servlet อาจจะเป็นอย่างข้างล่างนี้ (HelloWorld.java)
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld extends GenericServlet {
public void service(ServletRequest request, ServletResponse
response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<head>");
out.println("<title>Hello
World!</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello
World!</h1>");
out.println("</body>");
out.println("</html>");
}
}
คลาส HelloWorld เป็นตัวอย่างพื้นฐานที่อธิบายหลักการทำงานของ Servlet
ได้เป็นอย่างดี คลาสนี้เป็นคลาสที่ subclass คลาส GenericServlet
โดยทำการ override ฟังก์ชัน public void service(ServletRequest request,
ServletResponse response)... ซึ่งเป็นฟังก์ชันที่อยู่ใน Servlet interface
ฟังก์ชันนี้เป็นฟังก์ชันที่สำคัญที่สุดสำหรับทุก Servlet เพราะว่าเมิื่อใดก็ตามที่มี
request เข้ามายัง Servlet ฟังก์ชัน service ที่อยู่ใน Servlet ดังกล่าวจะถูกเรียกโดย
Servlet Engine เพื่อทำการประมวลผล request ที่เข้ามาเสมอ
GenericServlet เป็น abstract คลาสที่ทำการ implement ตัว Servlet
interface โดยทิ้งฟังก์ชัน service ให้เป็น abstract (ไม่มีส่วนที่เป็น implementation)
ซึ่งทางคลาสที่เป็น subclass ของ GenericServlet จะต้องมีหน้าที่ในการ
implement ฟังก์ชันนี้ยกตัวอย่างเช่นคลาส HelloWorld ของเราเป็นต้น หันกลับมาดู code ที่อยู่ใน HelloWorld กันบ้าง ในสามบรรทัดแรกเราจะเห็นว่าจะมีสาม
package ที่ถูก import เข้ามาซึ่งก็คือ java.io.*, javax.servlet.*
และ javax.servlet.http.* เราเรียก package java.io
เข้ามาเพราะว่าเราต้องการใช้คลาส IOException และ คลาส PrintWriter
ส่วน package javax.servlet และ javax.servlet.http เราจะใช้สำหรับเรียกคลาสที่เกี่ยวข้องกับ
Servlet ทั้งหมด
เรามาดู code ที่อยู่ในฟังก์ชัน service กันบ้าง ตัวฟังก์ชัน service จะมี
argument อยู่ด้วยกันสองตัวคือ ServletRequest และ ServletResponse
โดยเมื่อใดก็ตามที่ฟังก์ชัน service ถูกเรียกโดย Servlet Engine ตัว Servlet
Engine จะทำการส่งผ่านออฟเจคมายัง argument เหล่านี้ซึ่งก็คือ ServletRequest
และ ServletResponse ออฟเจคนั่นเอง ServletResponse
ออฟเจคเป็นตัวที่เก็บรายละเอียดของ Request ที่ส่งมาโดย client ทั้งหมดโดย
Servlet สามารถใช้ออฟเจคนี้เพื่ออ่าน parameters ต่าง ๆ ที่อยู่ใน Request
เข้ามาประมวลผลได้ หลังจากที่ Servlet ทำการประมวลผลเสร็จแล้ว Servlet ก็สามารถส่งผลที่ได้กลับออกไปให้
client ได้โดยใช้ ServletResponse ออฟเจค อย่างไรก็ตามฟังก์ชัน service
ที่อยู่ใน Hello World Servlet ของเราไม่ได้มีการประมวลผลใด ๆ เพียงแต่จะส่ง
HTML Stream ที่เขียนคำว่า "Hello World" กลับไปให้ Client เท่านั้น
ในการส่งผลต่าง ๆ กลับไปให้ Client ขั้นตอนแรกที่ Servlet ต้องทำคือการเซ็ต
Header โดยในตัวอย่างของเรา Header ที่ถูกส่งกลับไปก็คือ Content Type
(MIME Type) Header ซึ่งเป็นตัวบอก Client ว่า Stream ที่กำลังจะได้รับเป็น
Stream ชนิดไหน (ให้นึกถึง MIME Type เป็นลักษณะคล้าย ๆ กับไฟล์
extension) ยกตัวอย่างเช่น text/html จะบอกว่า Stream ที่กำลังส่งออกไปเป็น
HTML Stream หรือ image/gif จะบอกว่า Stream ที่ส่งออกไปเป็น Stream ของ gif
image เป็นต้น ตัว Servlet สามารถทำการเซ็ต Content
Type ได้โดยใช้ฟังก์ชัน setContentType() ซึ่งเป็นฟังก์ชันที่อยู่ใน
ServletResponse นั่นเอง ในการส่ง Stream ต่าง ๆ ไปให้
Client ตัว Servlet สามารถส่ง Stream ออกไปได้สองแบบคือ text Stream
และ byte Stream ในกรณีของเรา ๆ จะส่ง Stream ออกไปเป็นแบบ text Stream ดังนั้นคลาสที่เราจะใช้ในการเขียนลงไปที่
ServletResponse ก็คือคลาส PrintWriter นั่นเอง (สำหรับรายละเอียดของ Stream เราจะพูดถึงในบทความต่อ ๆ ไป)
ในการเรียกใช้คลาส PrintWriter สำหรับเขียนลงไปที่ ServletResponse
เราก็สามารถทำได้ง่าย ๆ โดยการเรียกฟังก์ชัน ServletResponse.getWriter()
ซึ่งจะ return ตัว PrintWriter instance ที่จะใช้ในการเขียน
output ต่าง ๆ ลงไปยังตัว ServletResponse ที่เราใช้สำหรับเรียก
instance ดังกล่าวขึ้นมา ท้ายสุด HelloWorld Servlet จะใช้ PrintWriter ทำการพิมพ์ text ต่าง ๆ ที่ใช้ในการสร้าง HTML
Output Stream ที่เขียนคำว่า "Hello World" ออกไปให้ Client
การรัน Servlet
Servlet เป็นคอนเซ็ปหนึ่งในจาว่าที่เราเขียนขึ้นเพื่อใช้งานสำหรับ Server
Side Application ถ้าใครเคยเขียน application โดยใช้จาว่ามาก่อนจะเห็นว่าตัว
entry point ของ application จะเป็น static ฟังก์ชันที่ชื่อ main() ยกตัวอย่างเช่น
public class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello World");
}
}
ตัว Servlet เองจะมีลักษณะคล้าย ๆ Applet ตรงที่ว่าในการรัน ฟังก์ชันที่ใช้เป็น
entry point จะไม่ใช่ฟังก์ชัน main() แต่จะกลายเป็นฟังก์ชันอื่นที่ถูก
define ไว้ใช้สำหรับตัวมันเองโดยเฉพาะยกตัวอย่างเช่นใน Applet ฟังก์ชันที่ถูกใช้เป็น
entry point ก็จะเป็นฟังก์ชันพวก init(), start() ซึ่งสำหรับ Servlet
แล้วฟังก์ชันที่ถูกใช้เป็น entry point ก็คือ service() ซึ่งก็คือฟังก์ชันที่ถูก
define อยู่ใน javax.servlet.Servlet interface นั่นเอง
ในการรัน Servlet สิ่งที่เราต้องการมีดังต่อไปนี้คือ
1) JDK
สำหรับคนที่ใช้ Windows, Sun หรือ Linux ให้ไปดาวโหลดที่ http://java.sun.com
สำหรับคนที่รัก Linux โดยเฉพาะอาจจะไปดาวโหลดที่นี่ก็ได้ http://www.blackdown.org
2) Servlet API Package and Servlet Engine
Package นี้เป็นตัวที่เก็บ Servlet API ทั้งหมดไว้ซึ่งก็คือ javax.servlet.*
และ javax.servlet.http.* package
ในการรัน Servlet เราสามารถดาวโหลดตัวที่เป็น Java Servlet Development
Kit จาก Sun ซึ่งเป็นตัวที่เก็บ Servlet API และตัว Servlet Engine พื้นฐานเอาไว้โดยเราสามารถดาวโหลดได้จาก
http://java.sun.com/products/servlet/download.html
(เลือกในส่วนที่เรียกว่า Java Servlet Development Kit) อย่างไรก็ตามสำหรับคนที่ต้องการเรียนรู้
Servlet อย่างจริงจังและต้องการที่จะนำไปใช้งานจริง ผู้เขียนขอแนะนำให้ใช้
Servlet Engine ที่เป็น standard ซึ่งโดยทั่วไปจะรวม Servlet API เข้าไปอยู่ในตัวเรียบร้อยแล้ว
อย่างที่กล่าวมาข้างต้นว่า Servlet Engine อาจจะเป็นส่วนหนึ่งของ Web Server
หรืออาจจะเป็นตัว add-on ก็ได้ ดังนั้นสำหรับคนที่มี Web Server เปล่า ๆ ที่ยังไม่มี
Servlet Engine ติดอยู่ก็อาจจะเลือก Servlet Engine ที่เป็นลักษณะ add-on
โดยติดเข้าไปเป็น extension module สำหรับ Web Server ของตัวเองก็ได้ แต่สำหรับคนที่ยังไม่มีทั้ง
Web Server และ Servlet Engine ก็อาจจะเลือกดาวโหลด Web Server ที่มี Servlet
Engine อยู่ในตัวแล้วก็ได สำหรับลิสของ Servlet Engine
ที่มีอยู่สามารถหาดูจากที่นี่ได้ http://www.javaskyline.com/serv.html
Servlet Engine ที่เราจะใช้ในการสาธิตการติดตั้งและรัน Servlet จะเป็น
Servlet Engine ที่มาจาก Apache Group ซึ่งถือว่าเป็น Servlet Engine และ
JSP* Reference Implementation ของ Sun โดยมี code name ที่ชื่อว่า Tomcat
* JSP = Java Server
Pages
Note: สำหรับผู้ที่สนใจการติดตั้ง Tomcat เป็นภาษาไทยสามารถหาอ่านได้จากที่นี่
การติดตั้ง
Tomcat
สมมุติว่าเราต้องการรัน HelloWorld Servlet ของเราที่ Context path ที่ชื่อ
MyWebApp โดยชี้ไปที่ไดเรคทรอรี่ที่ชื่อ D:\MyWebApplication\
เราก็สามารถทำการแอท Context path นี้เข้าไปใน Tomcat ได้โดยการเปิดไฟล์ที่ชื่อ
server.xml ซึ่งจะอยู่ในไดเรคทรอรี่ conf ของ Tomcat Home directory เสร็จแล้วให้ทำการเพิ่มบรรทัดข้างล่างนี้เข้าไปในส่วนของของ
<Context path> tag
<Context path="/MyWebApp" docBase="D:\MyWebApplication\" debug="0"
reloadable="true"/>
จากนั้นให้ทำการสร้าง subdirectory ที่ชื่อ WEB-INF ขึ้นมาใต้ D:\MyWebApplication\ไดเรคทรอรี่
เสร็จแล้วให้สร้างอีก subdirectory ที่ชื่อ classes ขึ้นมาใต้ WEB-INF อีกทีหนึ่งโดย
subdirectory classes นี้จะเป็นที่ ๆ เราใช้เก็บคลาส Servlet ทั้งหมด ซึ่งท้ายสุดไฟล์ HelloWorld.java ก็จะอยู่ใน directory stucture ดังนี้
D:\MyWebApplication\WEB-INF\classes\HelloWorld.java
จากนั้นให้ทำการ compile ไฟล์ HelloWorld เหมือนปกติ โดยถ้าใครคอมไฟล์นี้ไม่ได้
ให้เช็คดูว่า Servlet API ถูกรวมอยู่ใน CLASSPATH แล้วหรือยัง เสร็จแล้วให้ทำการ start ตัว Tomcat แล้วพิมพ์ http://127.0.0.1:8080/MyWebApp/servlet/HelloWorld
ก็จะได้ผลดังรูปข้างล่าง
รูปที่ 3. HelloWorld Servlet
Note: Tomcat จะทำการแมป /servlet ของ Context path เข้ากับ Servlet ต่าง ๆ ที่อยู่ใน subdirectory classes โดยอัตโนมัติ
เรามาดูตัวอย่างที่สองกันบ้าง ตัวอย่างนี้เป็น HelloWorld อีกแบบแต่แทนที่คลาส
Servlet ของเราจะทำการ subclass คลาส GenericServlet เราจะทำการ
subclass คลาส HttpServlet ซึ่งเป็น subclass ของคลาส GenericServlet
อีกทีหนึ่งแทน คลาส GenericServlet และคลาส HttpServlet
จะต่างกันตรงที่ว่าคลาส GenericServlet จะเป็นคลาสที่ไม่เจาะจงโปรโตคอลใดโปรโตคอลหนึ่งซึ่งเหมาะสำหรับเป็น
base class สำหรับ Servlet ที่ต้องการ implement ตัว Servlet ที่ใช้สำหรับโปรโตคอลจำเพาะเจาะจงขึ้นมาเองยกตัวอย่างเช่น
Servlet สำหรับ RMI, Servlet สำหรับ IIOP (CORBA) หรือแม้กระทั่ง Servlet
สำหรับ Http โปรโตคอลซึ่งก็คือ HttpServlet ที่เรากำลังพูดถึงอยู่นั่นเอง
คลาส HelloWorld (HelloWorld2.java)
แบบที่สองของเราก็จะเป็นอย่างข้างล่างนี้
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld2 extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse
response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<head>");
out.println("<title>Hello
World2</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello
World2</h1>");
out.println("</body>");
out.println("</html>");
}
}
ถ้าทำการรันก็จะได้ผลดังนี้
รูปที่ 4. HelloWorld2 Servlet
จะสังเกตเห็นว่าฟังก์ชันที่เราทำการ override ก็คือฟังก์ชัน service
แต่ต่างกันตรงที่ว่าฟังก์ชันนี้ไม่ใช่ฟังก์ชัน service(ServletRequest
req, ServletResponse res)... แต่กลับกลายเป็น service(HttpServletRequest
req, HttpServletResponse res)... แทนซึ่งเป็นฟังก์ชันที่จำเจาะจงสำหรับ
Http โปรโตคอลโดยเฉพาะ หลายคนอาจสงสัยว่าฟังก์ชัน service(HttpServletRequest
req, HttpServletResponse res)... จะสามารถถูกเรียกโดย Servlet Engine ได้อย่างไรในเมื่อ
Servlet Engine จะเรียกเฉพาะฟังก์ชัน service(ServletRequest req, ServletResponse
res)... ซึ่งเป็นฟังก์ชันที่ถูก define อยู่ใน javax.servlet.Servlet
interface เท่านั้น ถ้าใครเคยเข้าไปดู code ที่อยู่ใน HttpServlet จะเห็นว่าเป็นลักษณะคล้าย ๆ อย่างนี้
public void service(ServletRequest req, ServletResponse res) throws
ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP
request or response");
}
service(request, response); // service(HttpServletRequest
res, HttpServletResponse res) ...
}
จะเห็นว่าคลาส HttpServlet ก็ยังคงทำการ implement ฟังก์ชัน service(ServletRequest
req,...) ซึ่งเป็นฟังก์ชันที่อยู่ใน javax.servlet.Servlet interface
อยู่เพียงแต่ว่าเมื่อใดก็ตามที่มีการเรียกฟังก์ชันนี้โดย Servlet Engine ทาง
HttpServlet จะทำการ cast ตัว ServletRequest และ ServletResponse
ให้กลายเป็น HttpServletRequest และ HttpServletReponse
ตามลำดับ หลังจากนั้นจะส่งผ่าน object สองตัวนี้เข้าไปยัง service(request,
response) ซึ่งก็คือฟังก์ชัน service(HttpServletRequest req, HttpServletResponse
res)... อีกต่อหนึ่งโดยโค๊ดของเราที่พิพม์คำว่า HelloWorld ที่อยู่ในฟังก์ชันนี้ก็จะถูกเรียกโดย
Servlet Engine ในท้ายสุด
ตัวอย่าง HelloWorld Servlet (HelloWorld3.java)
สุดท้ายที่กำลังจะกล่าวถึงนี้เป็นตัวอย่างของ HelloWorld Servlet
ที่ถูกยกขึ้นมาใช้เป็นตัวอย่างของ HelloWorld Servlet ในแทบทุก Servlet
Tutorial ที่มีอยู่ในโลกนี้ซึ่งที่มาที่ไปของ Servlet นี้ก็มาจากสอง
HelloWorld Servlet ที่เราพูดถึงข้างต้นนั่นเอง
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<head>");
out.println("<title>Hello
World3</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello
World3</h1>");
out.println("</body>");
out.println("</html>");
}
}
เราจะเห็นว่า Servlet นี้ไม่ได้ทำการ override ฟังก์ชัน service
แต่อย่างใดแต่กลับทำการ override ฟังก์ชัน doGet() แทน
ฟังก์ชัน doGet() นี้เป็นฟังก์ชันที่ถูก define อยู่ใน HttpServlet
เพื่อรับ request ที่มาจาก client ในแบบ Http GET Method ซึ่งก็คือ request
ที่มาจาก URL ธรรมดาทั่ว ๆ ไปนั่นเอง นอกจากฟังก์ชัน doGet() แล้วคลาส HttpServlet ยังมีฟังก์ชันอื่น ๆ ที่อนุญาติให้เราสามารถทำการ
override เพื่อรองรับ request แบบอื่น ๆ ได้อีก ยกตัวอย่างเช่น
doPost() ใช้สำหรับรับ request ที่มาจาก client ในแบบ POST Method (Form request)
doDelete() ใช้สำหรับรับ request ที่มาจาก client ในแบบ DELETE Method เพื่ออนุญาติให้ client ทำการลบ resource บางอย่างออกจาก server ได้
ยังมีฟังก์ชันอีกสามอันคือ doPut(), doOptions() และdoTrace() ที่อยู่ในคลาส HttpServlet เพื่อรองรับ request
แบบ PUT,OPTIONS และ TRACE แต่เราไม่นิยมทำการ override ฟังก์ชันเหล่านี้ด้วยเหตุที่ว่าส่วน implementation ของฟังก์ชันเหล่านี้ที่อยู่ในคลาส
HttpServlet ได้ถูก implement มาอย่างดีเหมาะสำหรับการใช้งานทั่ว ๆ ไปแล้ว
รูปที่ 5. HelloWorld3 Servlet
หลายคนอาจจะสงสัยอีกครั้งว่าทำไมฟังก์ชัน doGet(), doPost()
และอื่น ๆ สามารถถูกเรียกได้ในเมื่อ Servlet Engine จะเรียกฟังก์ชัน service(ServletRequest req,...) ซึ่งจะส่งผ่านไปยัง service(HttpServletRequest req,...)
เท่านั้น เหตุผลง่าย ๆ ก็คือในกรณีทั่ว ๆ ไปเรามักไม่นิยม override ฟังก์ชัน service(HttpServletRequest req,...) ที่อยู่ใน HttpServlet
อย่างที่เราทำใน HelloWorld2 เพราะว่าในฟังก์ชัน service(HttpServletRequest req,...) ได้มีส่วนที่เป็น implementation บรรจุอยู่แล้วโดย code
ส่วนนี้จะเป็นส่วนที่ทำการส่งผ่าน call ของ Servlet Engine ไปยังฟังก์ชัน doXXX() ต่าง ๆ ดัง code snippet ข้างล่าง
protected void service(HttpServletRequest req, HttpServletResponse
resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
...
doGet(req, resp);
...
} else if (method.equals(METHOD_HEAD)) {
...
doHead(req, resp);
// will be forwarded to doGet(req, resp)
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
...
}
}
}
จะเห็นว่าถ้าเราไม่ทำการ override ฟังก์ชัน service นี้แล้ว ฟังก์ชันนี้ก็จะทำการส่ง
request ไปให้ฟังก์ชันย่อยต่าง ๆ ที่รับผิดชอบ Http Method ต่าง ๆ ซึ่งในกรณีของเราก็คือฟังก์ชัน doGet() นั่นเอง
ทิ้งท้าย
บทความนี้เราได้พูดถึงหลักการทำงานแบบกว้าง ๆ ของ Servlet โดยใช้ HelloWorld
Servlet เป็นตัวอย่าง ในบทความหน้าเราจะคุยกันถึง Life Cycle ของ Servlet
ตั้งแต่การถูกโหลดขึ้นมาใช้เป็นครั้งแรก, การประมวลผล Request ที่มาจาก client
โดย Servlet ตลอดจนถึงการ clean up resources ต่าง ๆ ของ Servlet เมื่อ Servlet
กำลังจะถูก unload ออกจาก Servlet Engine
Copyright (C)
2000 www.jarticles.com.
|