Saturday, August 4, 2012

Introduction to Java Servlet (in depth)

,

เกริ่นนำ
แม้ว่าโลกของ 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

0 comments to “Introduction to Java Servlet (in depth)”

Post a Comment

 

Java Servlet Copyright © 2012 | Design by I Love Linux