Saturday, August 4, 2012

ติดตั้ง tomcat 6 ร่วมกับ apache อย่างไรครับ

,
คือ จะเขียนสคริปต์ jsp และ php อะครับ
ต้องใช้ทั้ง apache และ tomcat จะติดตั้ง อย่างไร ครับ
แบบง่ายๆ apt-get เลยได้ไหม
เพราะเห็นตามเว็บ เจอแต่ ยากๆ

 ผมเคยเขียนเก็บไว้บน google doc ก็เลยเอามา post ให้ครับ (ของผมใช้ java5 กับ tomcat5 ถ้าจะใช้ tomcat6 ให้ลงเป็น java6 นะครับ)


1. upload this file to application server

jakarta-tomcat-5.0.28.tar.gz

jdk-1_5_0_17-linux-i586.bin


2. make working directory

#mkdir /home/opt

#mv  jakarta-tomcat-5.0.28.tar.gz  jdk-1_5_0_17-linux-i586.bin  /home/opt


3. install j2sdk

#./jdk-1_5_0_17-linux-i586.bin

result:   jdk1.5.0_17   --->   directory


4. extract tomcat tarball

#tar -xvf  jakarta-tomcat-5.0.28.tar.gz

result:   jakarta-tomcat-5.0.28   --->  directory


5. setup program for start at boot time

#cp  /home/opt/jakarta-tomcat-5.0.28/bin/catalina.sh  /etc/init.d

then, add this line


# OS specific support.  $var _must_ be set to either true or false.

JAVA_HOME=/home/opt/jdk1.5.0_17
CLASSPATH=/home/opt/jdk1.5.0_17/jre/lib/rt.jar
CATALINA_HOME=/home/opt/jakarta-tomcat-5.0.28
PATH=$PATH:/home/opt/jdk1.5.0_17/bin
export JAVA_HOME
export CLASSPATH
export CATALINA_HOME
export PATH


6. edit global environment

#pico /etc/profile

then, add this lilne


JAVA_HOME=/home/opt/jdk1.5.0_17
CLASSPATH=/home/opt/jdk1.5.0_17/jre/lib/rt.jar
CATALINA_HOME=/home/opt/jakarta-tomcat-5.0.28
PATH=$PATH:/home/opt/jdk1.5.0_17/bin
export JAVA_HOME
export CLASSPATH
export CATALINA_HOME
export PATH

umask 022     --->   last line (old configuration)


7. startup test

#/etc/init.d/catalina.sh start

result:

Using CATALINA_BASE:   /home/opt/jakarta-tomcat-5.0.28
Using CATALINA_HOME:   /home/opt/jakarta-tomcat-5.0.28
Using CATALINA_TMPDIR: /home/opt/jakarta-tomcat-5.0.28/temp
Using JAVA_HOME:       /home/opt/jdk1.5.0_17

and

root@catserver:~# netstat -lnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:25              0.0.0.0:*               LISTEN
tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN
tcp6       0      0 :::8009                 :::*                    LISTEN
tcp6       0      0 :::8080                 :::*                    LISTEN        --->      tomcat already start
tcp6       0      0 :::22                   :::*                    LISTEN


8. open web browser and type

http://your server ip address:8080


9. configure tomcat user and password for web management

#pico  /home/opt/jakarta-tomcat-5.0.28/conf/tomcat-users.xml

result:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>
</tomcat-users>

edit:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="manager" password="hahaha" roles="manager"/>
  <user username="admin" password="hahaha" roles="admin"/>
</tomcat-users>

*now! you can login with manager and admin account


10. change permission for security

 #chmod 600 /home/opt/jakarta-tomcat-5.0.28/conf/tomcat-users.xml


11. restart tomcat (re-configure)

#/etc/init.d/catalina.sh stop

#/etc/init.d/catalina.sh start


12. login with admin and manager account


finish

*จริงๆ apt-get เอาก็ได้นะครับ ทั้ง java6 , tomcat6 แต่ต้องตั้งค่า environment ให้ถูกครับ และถ้าผมจำไม่ผิดนะครับ ubuntu server 8.10 ตอนที่ format เครื่องเพื่อลง OS มันจะมีให้เลือกนะครับว่าจะติดตั้ง server แบบไหน ผมว่าผมเคยเห็นว่ามี tomcat ให้เลือกนะครับ ถ้าเลือกตรงนั้น พอ format เสร็จก็จะได้ tomcat server แบบสมบูรณ์ไปเลย

ขอบคุณครับ

JavaServer Pages (JSP) Part I

,
JavaServer Pages (JSP) เป็น web-scripting เทคโนโลยีคล้ายกับ Netscape server-side JavaScript (SSJS) หรือ Microsoft Active Server Pages (ASP) แต่ผิดกันตรงที่หัวใจของ JSP คือ Java ซึ่งเป็นภาษาที่คอนเซ็ปหลักอยู่ที่ออฟเจ็ค (object-oriented style) ซึ่งช่วยทำให้ง่ายต่อการพัฒนาในโปรเจคใหญ่ ๆ ตลอดจนสามารถนำส่วนประกอบต่าง ๆ กลับมาใช้ได้อีก (software reusable) จุดเด่นที่สำคัญของ JSP คือสามารถทำงานได้โดยไม่ขึ้นอยู่กับผู้ผลิตซอฟแวร์รายใดรายหนึ่งโดยเฉพาะ ซึ่งโดยทั่วไปเทคโนโลยีต่าง ๆ มักจะออกมาในลักษณะของผลิตภัณฑ์จากบริษัทผู้ผลิตแห่งใดแห่งหนึ่ง แต่ JSP ใช้ลักษณะของ specification ซึ่งกำหนดโดย Sun Microsystems ดังนั้นผู้ผลิตซอฟแวร์จึงสามารถอ้างอิง specification ที่กำหนดขึ้น ผลิต JSP Container (ตัวที่ใช้ในการรัน JSP) ขึ้นมาใช้กับแฟลตฟอร์มใดก็ได้ 
JSP Containers
JSP Pages (ไฟล์ที่เขียนขึ้นโดยใช้ JSP script และลงท้ายด้วย .jsp) จะถูกรันโดย JSP Container ซึ่งมักจะเป็นส่วนประกอบที่อยู่ใน Webserver หรือ เป็นตัวแอทออนใน Application Server.  โดยทั่วไป JSP Container จะเป็นตัวรับ request จาก client ส่งผ่านไปยัง JSP Page และส่งค่าที่ได้จากการประมวลผลโดย JSP Page กลับไปยัง client.  JSP Container ที่ใช้กันอยู่มีมาจากหลายค่าย ยกตัวอย่างเช่น GNU JSP, Expresso, Tomcat Jakarta, Resin, Weblogic เป็นต้น 
JSP Container ที่เราจะใช้กันคือ Jasper ซึ่งเป็น JSP Container ที่อยู่ใน Tomcat Servlet Engine จากค่าย apache (www.apache.org) ซึ่งทาง Sun ใช้เป็นตัวอ้างอิงในวงการ JSP โดยในอนาคต Tomcat จะเป็นตัวที่ใช้ในการรัน Servlet แทน Jserv ซึ่งใช้กันอยู่ในปัจจุบัน
การติดตั้ง Tomcat
1) Java Development Kit
โปรแกรม Tomcat ถูกเขียนขึ้นโดยใช้จาว่า ซึ่งจะรันกับ Java Development Kit (JDK) เวอร์ชัน 1.1 และ 1.2 ดังนั้นก่อนจะรัน เราจะต้องติดตั้ง JDK1.xxx ตามแฟลตฟอร์มที่เราใช้เสียก่อน
สำหรับวินโดว์และยูนิกส์ให้ไปดาวโหลดที่ http://java.sun.com/jdk/. (บางครั้ง JDK อาจถูกเรียกว่า SDK)
สำหรับ Linux ให้ไปที่ http://www.blackdown.org/.
สมมุติว่าเราใช้วินโดว์ และติดตั้ง JDK1.2.2 ไว้ที่ D:\jdk1.2.2 ให้เซ็ต PATH=d:\jdk1.2.2\bin\ โดยใช้คำสั่งใน Dos (set PATH=d:\jdk1.2.2\bin\;) หรือ คลิกขวาที่ My Computer แล้วเลือกที่ Environment แทป
ที่เราต้องเซ็ต PATH เพราะ Tomcat จะรันโดยใช้คำสั่ง java.exe ซึ่งอยู่ใน bin ไดเรคทรอรี่ของ JDK
2) Jakarta Tomcat
ทาง apache ยังคงทำการพัฒนา Tomcat ให้มีประสิทธิภาพมากเพื่อใช้เป็นมาตราฐานในอนาคต ดังนั้นถ้าเข้าไปที่เวปไซด์ในส่วนของดาวโหลด เราจะเห็นตัวเลือกในการดาวโหลดได้คร่าว ๆ เป็นสองแบบ คือ แบบ souce files ที่ต้องมาคอมไพล์เองโดยใช้เครื่องมือช่วยที่เรียกว่า Ant ซึ่งเป็นส่วนที่นักพัฒนานิยมดาวโหลดกัน กับแบบที่คอมไพล์แล้ว (binary) พร้อมที่จะรันซึ่งเหมาะสำหรับคนที่อยากนำ Tomcat มาใช้งานอย่างเดียว และเป็นอย่างที่เราจะใช้ ดังนั้น ให้ไปที่ http://jakarta.apache.org/downloads/binindex.html แล้วทำการติดตั้ง
สมมุติว่าเราใส่ Tomcat ไว้ที่ D:\jakarta-tomcat ให้เปิด Command Prompt (MSDos) จาก Application Menu หรือใช้ start->run->type cmd แล้วไปที่ไดเร็คทรอรี่ที่เป็น bin ซึ่งในตัวอย่างก็คือ D:\jakarta-tomcat\bin แล้วรันโดยใช้คำสั่ง D:\jakarta-tomcat\bin> startup หลังจากนั้นให้เปิด browser เพื่อเช็คว่า Tomcat ทำงานถูกต้องหรือเปล่า โดยไปที่ http://127.0.0.1:8080/examples/jsp/ 
Note: http://127.0.0.1:8080 คือ ให้ browser ส่ง request ไปที่เครื่องตัวเอง (localhost) ที่พอร์ต 8080 (Tomcat default port)
ถ้าจะปิด Tomcat ก็ให้พิมพ์ D:\jakarta-tomcat\bin\> shutdown ลงไปแทน
Web Applications 
ก่อนที่เราจะเริ่มเขียน JSP สิ่งหนึ่งที่ควรจะทำความเข้าใจคือโครงสร้างของไดเรคทรอรี่ต่าง ๆ สำหรับไฟล์ของเราใน Tomcat ในสมัยก่อนเราไม่ค่อยได้ให้ความสนใจกับการจัดเรียงไฟล์ต่าง ๆ ซักเท่าไหร่ ข้อเสียที่เห็นได้ชัดคือการยากในการจัดเก็บ และการยากในการย้ายไฟล์จากเซฟเวอร์หนึ่ง ไปยังอีกเซฟเวอร์หนึ่ง เพราะโดยทั่วไปแต่ละ webserver จะมีลักษณะการจัดเก็บไฟล์ไม่เหมือนกัน แต่เมื่อ Java Servlet Specification เวอร์ชั่น 2.2 ออกมา เซฟเวอร์ที่ใช้รัน Servlet v2.2 จะถูกบังคับให้มีต้องการสนันสนุนการจัดเก็บไฟล์แบบหนึ่ง ที่เรียกว่า Web Applicaton 
web application  คือกลุ่มของไดเรคทรอรี่และไฟล์ที่อาจจะประกอบด้วย html, jsp, servlet, javabean และอื่น ๆ ซึ่งอยู่รวมกันในลักษณะของระบบไฟล์ (file system) หรือถูกอัดอยู่ในไฟล์เดียวกันโดยจะเรียกว่า Web Archive (.war) ไฟล์ ซึ่งมีประโยชน์ในการโยกย้ายและติดตั้งจากเซฟเวอร์หนึ่งไปยังอีกเซฟเวอร์หนึ่ง
Tomcat เป็น Servlet Engine ที่สนันสนุน Java Servlet API v.2.2 ดังนั้นไฟล์ JSP ที่เราเขียน จะต้องถูกจัดอยู่ในรูปแบบ Web Application นี้ด้วย
ถ้าใครเคยคลุกคลีกับ webserver จะเห็นว่ามีการใช้ไดเรคทรอรี่ในการจัดกลุ่มของไฟล์ไว้ด้วยกันตามหน้าที่ ซึ่งทาง Tomcat ก็ใช้หลักการเดียวกันในการจัดการ web application หลักการก็คือ แต่ละ web appliction จะถูกจัดให้อยู่ในไดเรคทรอรี่ของตัวเอง ที่เรียกว่า context path ยกตัวอย่างเช่น ถ้า web application ของเราถูกแมปกับ context path ที่ชื่อ webapplication1 เวลาดูจาก URL ก็จะเห็นเป็น http://www.mycompany.com/webapplication1/ เป็นต้น
อย่างที่กล่าวมาข้างต้นว่า web application คือกลุ่มของไดเรคทรอรี่และไฟล์ที่ถูกจัดอยู่รวมกันเป็นชั้น ๆ ดังนั้นถ้าเรามีไฟล์ที่ชื่อ helloworld.html :ซึ่งอยู่บนสุด (top-level) ของไดเรคทรอรี่ใน web application เราก็จะสามารถเรียกออกมาดูได้โดยใช้ URL เท่ากับ http://www.mycompany.com/webapplication1/helloworld.html
ในกรณีที่ไฟล์ไม่ได้อยู่บนสุดของไดเรคทรอรี่ใน web application เราก็ยังสามารถเรียกออกมาได้โดยอ้างอิงไปที่ไดเรคทรอรี่ย่อยนั้น ยกตัวอย่างเช่น ถ้ากลุ่มไฟล์ JSP ถูกเก็บไว้ในไดเรคทรอรี่ถัดลงมาที่ชื่อ jsp เราก็สามารถที่จะเรียกไฟล์มาดูโดยใช้ URL เท่ากับ http://www.mycompany.com/webapplication1/jsp/xxx.jsp 
ส่วนประกอบของ Web Application
web application หนึ่ง ๆ อาจประกอบด้วย
1) Servlet
2) JavaServer Pages
3) Utility Classes
4) ไฟล์ปกติ เช่น HTML, images, sounds 
5) JavaBean
แต่ละอย่างจะถูกเก็บไว้ในไดเรคทรอรี่ต่อไปนี้
1) root directory หรือส่วนบนสุด : ไฟล์ *.html, *.jsp มักจะถูกเก็บไว้ส่วนบนสุดของ web application แต่อาจจะมีไดเรคทรอยีย่อยลงมาจาก root เพื่อเก็บ ไฟล์ images ก็ได้ 
2)  WEB-INF/web.xml : ไฟล์ web.xml เป็นส่วนที่ใช้ในการบรรยายส่วนประกอบต่าง ๆ (Web Application Deployment Descriptor) รวมไปถึงการป้อนค่าเริ่มต้นให้กับโปรแกรมต่าง ๆ ที่บรรจุอยู่ใน web application นี้ ยกตัวอย่างเช่น เราสามารถป้อนค่าเริ่มต้นให้กับ Servlet ได้โดยใส่ค่าเข้าไปใน XML Tag ตรงส่วนที่เรียกว่า <init-param> เป็นต้น
(หาอ่านเพิ่มเติมได้จาก JavaServlet Specification v2.2, http://java.sun.com/products/servlet/)
3) WEB-INF/classes/* : เป็นส่วนที่ใช้ในการเก็บไฟล์ *.class ทั้งหมดที่ใช้ใน web application ซึ่งอาจจะเป็นทั้ง Servlet หรือ ไฟล์ class ธรรมดาที่ไม่ได้อยู่ในรูปของ .jar ก็ได้ ยกตัวอย่างเช่น ถ้าไฟล์ของเราอยู่ใน package ชื่อ com.mycompany.mypackage.MyServlet ไฟล์นี้ก็จะถูกเก็บอยู่ในไดเร็คทรอรี่ WEB-INF/classes/com/mycompany/mypackage/MyServlet.class
(คอนเซ็ปเกี่ยวกับ package สามารถอ่านเพิ่มเติมได้จาก JAVA PACKAGE)
4) WEB-INF/lib/*.jar : ไดเร็คทรอรี่นี้ใช้เก็บไฟล์ .jar ที่เราต้องการใช้สำหรับ web application ซึ่งอาจจะเป็นส่วนที่ได้มาจาก third party หรือเป็นพวก Database driver ก็ได้ ประโยชน์อย่างหนึ่งที่เห็นได้ในการเก็บไลบารี่ต่าง ๆ ไว้ที่ไดเร็คทรอรี่นี้คือ การง่ายต่อการติดตั้ง เหตุผลคือ หลังจาก web application ของเราถูกใส่เข้าไปใน server แล้ว ตัว server จะโหลดไฟล์ที่อยู่ในไดเร็คทรอรี่นี้โดยอัตโนมัติ เราจึงไม่ต้องทำการเซ็ต CLASSPATH ให้ยุ่งยาก
ตัวอย่างง่าย ๆ ของโครงสร้าง web application อาจเป็น
/index.html
/howto.jsp
/feedback.jsp
/images/banner.gif
/images/jumping.gif
/WEB-INF/web.xml
/WEB-INF/lib/jspbean.jar
/WEB-INF/classes/com/mycompany/servlets/MyServlet.class
/WEB-INF/classes/com/mycompany/util/MyUtil.class 
Integration with Tomcat 
การติดตั้ง web application สามารถทำได้ 3 วิธีคือ 
1) นำ web application ที่อยู่ในรูปของกลุ่มไดเรคทรอรี่ (ยังไม่ได้ถูกบีบอัด) ไปใส่ไว้ที่ $TOMCAT_HOME/webapps/.  กรณีนี้ Tomcat จะจัด context path ให้ web application เองโดยอิงจากชื่อ subdirectory ที่บรรจุกลุ่มไดเรคทรอรี่ของ web application ไว้ ยกตัวอย่างเช่น เราใส่ web application ไว้ที่ $TOMCAT_HOME/webapps/myapp/ ดังนั้น context path ที่จะใช้ติดต่อกับ web application นี้ก็คือ myapp วิธีนี้เป็นวิธีที่ง่ายที่สุด และเหมาะสำหรับการพัฒนาก่อนนำไปใช้จริง 
2) นำ web application archive (.war ไฟล์) ไปใส่ไว้ที่ $TOMCAT_HOME/webapps/.  วิธีนี้เหมาะสำหรับการติดตั้ง web application ใน production server ที่ใช้งานจริง โดย Tomcat จะแกะและขยายไฟล์ทั้งหมดที่อยู่ใน .war ไฟล์ออกมาแล้วทำการโหลดเข้าหน่วยความจำ 
3) ในกรณีที่ไดเรคทรอรี่ที่เราพัฒนา web application ไม่ได้อยู่ที่ $TOMCAT_HOME/webapps/ เราก็สามารถบอก Tomcat ให้โหลด web application นี้ได้โดยเข้าไปแก้ที่ $TOMCAT_HOME/conf/server.xml ในส่วนของ <Context> ให้ชี้ไปที่ไดเรคทรอรี่ที่เราเก็บ web application ไว้ เช่น ถ้าเราเก็บ web application ไว้ที่ D:\MyWebApplication\ แล้วอยากให้ชื่อ MyWebApp เป็น context path ในการชี้ไปที่ web application ของเรา เราก็แค่แอท 
<Context path="/MyWebApp" docBase="D:\MyWebApplication\" debug="0" reloadable="true"/> 
ไว้ที่บรรทัดถัดมาจาก Context  path อันสุดท้ายที่มีอยู่แล้ว เป็นต้น 
(สมมุติว่าเราติดตั้ง Tomcat ไว้ที่ ไดเรคทรอรี่ D:\jakarta-tomcat ดังนั้น $TOMCAT_HOME จะเป็น D:\jakarta-tomcat)  My Hello JSP
คอนเซ็ปของ JSP คือการใส่ Servlet code ลงไปใน HTML ไฟล์โดยผ่านทางสคิปต์ที่ขึ้นต้นด้วย <% และลงท้ายด้วย %> ดังตัวอย่างต่อไปนี้
<HTML>
<HEAD>
<TITLE>My Hello JSP</TITLE>
</HEAD>
<BODY>
<H1>Everybody says 
<% String str = "Hello JSP";
     out.println(str); %>
</H1>
</BODY>
</HTML>
อย่างที่กล่าวมาข้างต้น การที่จะรัน JSP ได้ เราจะต้องจัดไฟล์ต่าง ๆ ให้อยู่ใน web application  ดังนั้นถ้าใครอยากรันทดสอบ ก็ให้สร้างไดเรคทรอรี่ขึ้นมาอันหนึ่ง สมมุติว่าชื่อ D:\MyWebApplication\ โดยไดเรคทรอรี่นี้จะเป็น root directory ของ web application ที่เราจะใช้เก็บไฟล์ JSP ทุกไฟล์ที่เราเขียน หลังจากนั้นให้สร้าง subdirectory ที่ชื่อ jsp แล้วก๊อปปี้โค๊ดข้างบนไปใส่ไว้ในไฟล์ที่ชื่อ hellojsp.jsp แล้วเก็บไว้ที่ subdirectory jsp ที่เราเพิ่งสร้างขึ้น ดังรูป

รูปที่ 1 hellojsp.jsp
ถ้าสังเกตดี ๆ จะเห็นอีก subdirectory ชื่อ Web-inf ให้สร้าง subdirectory นี้ขึ้น แล้วใส่ไฟล์ web.xml ซึ่งเป็นไฟล์ที่ใช้บรรยายส่วนประกอบต่าง ๆ (Web Application Deployment Descriptor) ของ web application ของเราเข้าไป ซึ่งถ้าเปิดดู จะไม่เห็นอะไรมากในตอนนี้
หลังจากนั้นให้ไปแก้ $TOMCAT_HOME/conf/server.xml ไฟล์เพื่อลิงค์  Context Path (สมมุติว่าชื่อ MyWebApp) เข้ากับ web application ของเรา พอเสร็จก็ทำการ startup Tomcat, เปิด browser  แล้วพิมพ์  http://127.0.0.1:8080/(your context)/jsp/hellojsp.jsp ผลก็จะออกมาดังรูป 

รูปที่ 2 hellojsp.jsp in action
เกิดอะไรขึ้นบ้าง  ?
ถ้าดูเผิน ๆ จากทาง browser การเรียกไฟล์ JSP คงดูคล้าย ๆ กับการเรียกไฟล์ HTML ธรรมดา ๆ  แต่ความจริงแล้ว  หลังจาก request จาก client ส่งมาถึงเซฟเวอร์, JSP Container จะทำการแปลงไฟล์ JSP ให้กลายเป็น Servlet  source ไฟล์ (ในกรณีที่ JSP ไฟล์ดังกล่าวถูกเรียกเป็นครั้งแรก) ซึ่งไฟล์ Servlet source ที่ได้จะถูกคอมไพล์เป็น .class เพื่อใช้ในการประมวลผล request ของ client แล้วส่งกลับไปให้เซฟเวอร์ในรูปของ outputStream ซึ่งถูกส่งไปที่ client ในท้ายสุด
หลังจากนั้น ถ้า JSP ไฟล์ดังกล่าวถูกเรียกอีก การแปลงไฟล์หรือคอมไพล์จะไม่เกิดขึ้น เพราะ JSP  Container จะใช้ไฟล์ .class ที่เก็บไว้แล้วประมวลผลแทน  ข้อสังเกตคือ การเรียกไฟล์  JSP ครั้งแรกจะรู้สึกว่าช้า แต่ครั้งถัดไป ๆ จะเห็นได้ว่าเร็วขึ้นมาก เพราะได้มีการลดขั้นตอนต่าง ๆ ที่เสียเวลาไป
ใน JSP Specification 1,1 ยังมีการแนะนำให้ JSP Engine รองรับการโหลดไฟล์ JSP ขึ้นมาคอมไพล์ใหม่แบบอันโนมัติ (Reloading) ซึ่งมักจะเกิดขึ้นในกรณีที่ JSP ไฟล์ได้มีแก้ไข แล้วจะต้องมีการคอมไพล์ไฟล์ Servlet ที่กำลังใช้งานอยู่ใหม่

รูปที่  3 การแปลงและคอมไพล์ JSP ไฟล์
จากข้อความที่อธิบายข้างต้น เราสามารถแบ่งระยะเวลาของไฟล์ JSP หนึ่ง ๆ ออกเป็นสองช่วงคือ 
1) Translation Time คือช่วงเวลาที่ JSP ไฟล์ถูกแปลงให้กลายเป็น Servlet ไฟล์และถูกคอมไพล์ให้กลายเป็น .class ไฟล์ ซึ่งจะเกิดก่อนการรับ request จาก client ครั้งแรก
2) Client Request Time คือช่วงเวลาที่ .class ของ JSP ไฟล์ ทำการรับ request จากแต่ละ client แล้วทำการประมวลผล
JSP Translation
จากตัวอย่างไฟล์ hellojsp.jsp เราจะเห็นว่าโค๊ดจะมีแค่สองส่วนคือ ส่วนที่เป็น HTML code และส่วนที่เป็น Java code ซึ่งถูกบรรจุอยู่ใน <%  ... %> 
ในขั้นตอนของการแปลงไฟล์จาก .jsp เป็น Servlet  souce ไฟล์ (.java) ส่วนที่เป็น HTML code จะถูกเปลี่ยนให้อยู่ในรูปที่เทียบเท่ากับ out.print("HTMLCODE");  ไฟล์ hellojsp.jsp อาจถูกแปลงเป็น .java ได้คร่าว ๆ ดังนี้ 
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class _hellojsp1_xjsp extends HttpServlet {
  public void service(HttpServletRequest request, HttpServletResponse response) 
                                     throws ServletException, IOException {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<HTML>"); 
    out.println("<HEAD>");
    out.println("<TITLE>My Hello JSP</TITLE>");
    out.println("</HEAD>");
    out.println("<BODY>");
    out.println("<H1>Everybody says, ");
    // Java code inside <% 
    String str = "Hello JSP";
    out.println(str);
    // End of java code %>
    out.println("</H1>");
    out.println("</BODY>");
    out.println("</HTML>");
  }
}
* เพื่อที่จะเข้าใจหลักการทำงานของ JSP ผู้อ่านควรมีความเข้าใจ Java Servlet เป็นพื้นฐาน ถ้ามีเวลาว่าง ผู้เขียนอยากให้ช่วยอ่าน Java Servlet tutorial ซักเล็กน้อยเสียก่อน 

Servlet - Writing clean-cut Servlets with Template Engine การเขียน Servlet โดยใช้ Template Engine

,

เกริ่นนำ
ถ้าใครเคยอ่านตัวอย่างโค๊ดของ Servlet ที่อยู่ใน tutorial ต่าง ๆ ไม่ว่าจะเป็นตามหนังสือหรือตามเวปก็ตาม เรามักจะเห็นว่าหลังจากที่ Servlet ทำการประมวลผลที่มาจาก client แล้ว ท้ายสุดตัว Servlet นั้นจะต้องทำการสร้าง html เพจกลับไปให้ client ดังกล่าวโดยวิธีการสร้าง html ก็ทำกันแบบง่าย ๆ คือการใส่ html โค๊ดเข้าไปอยู่รวมกับส่วนที่เป็น java โค๊ดของคลาส Servlet นั้น ๆ เลย ตัวอย่างโค๊ดในลักษณะนี้ก็อาจเป็นแบบ code snippet ข้างล่าง public class MyServlet extends HttpServlet  {
...
 public void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws IOException, ServletException { 
 ...
    sb.append("<TR ");
    sb.append(CCStyles.LGREY);
    sb.append("><TD align=\"center\" valign=\"middle\">");
    sb.append(spellitem.id);
    sb.append("</TD><TD valign=\"middle\" ");
    sb.append(CCStyles.DGREY);
    sb.append(">");
    sb.append(spellitem.word);
    sb.append("</TD><TD valign=\"bottom\">");
    sb.append(to_continue);
    sb.append(spellitem.text);
    sb.append("</TD><TD valign=\"bottom\">");
    sb.append(spellitem.suggestions);
    sb.append("</TD></TR>\n"); 
... 
    response.setContentType("text/html"); 
    PrintWriter out = response.getWriter(); 
    out.print(sb.toString());
    out.close();
    } 

สำหรับเพจที่ไม่ซับซ้อนมากนัก การใส่ html โค๊ดเข้าไปรวมกับ java โค๊ดก็คงไม่มีปัญหาอะไร แต่ถ้าสมมุติว่า html เพจนั้นมี layout ที่ซับซ้อน ผลที่จะตามมาก็คือจำนวนโค๊ด html ที่มากขึ้น ซึ่งถ้าเราต้องเอา html โค๊ดดังกล่าวใส่เข้าไปรวมกับ java โค๊ดใน Servlet แล้ว เราก็มักจะพบกับปัญหาที่ตามมามากมาย
ปัญหาที่เกิดขึ้น
ปัญหาแรกที่เห็นได้ชัดคือ ความซับซ้อนที่มากขึ้นของระบบ ปัญหานี้มักถูกมองข้ามในกรณีที่เวปไซด์ที่ใช้ Servlet ในการสร้างเพจมีขนาดเล็ก ขนาดเล็กในที่นี้อาจเปรียบเทียบได้กับจำนวนเพจที่ตัว Servlet สามารถสร้างขึ้นมาได้ อย่างไรก็ตามสำหรับเวปไซด์ที่มีขนาดใหญ่ตั้งแต่ 10 เพจจนถึงร้อย ๆ เพจ การใส่ html เข้าไปรวมกับ java โค๊ดมักจะเป็นสิ่งที่่น่าปวดหัวสำหรับนักพัฒนาอย่างเรา ๆ เป็นอย่างมาก
ปัญหาถัดมาคือ ความเร็วในการพัฒนาระบบ นักพัฒนาไม่สามารถที่จะพัฒนาระบบได้เร็วมากนัก ด้วยเหตุผลที่ว่านักพัฒนาจะต้องเสียเวลากับการแก้ไขและตรวจสอบ html ต่าง ๆ ที่อยู่ใน java โค๊ดให้ถูกต้อง ซึ่งผลที่เห็นอย่างชัดเจนอย่างหนึ่งก็คือ แทนที่นักพัฒนาจะเสียเวลากับการ debug ตัว java โค๊ดแต่เพียงอย่างเดียว นักพัฒนายังจะต้องเสียเวลากับการ debug ส่วนที่เป็น html ต่าง ๆ ด้วย
ปัญหาที่เห็นได้ชัดอีกอย่างหนึ่งก็คือ ปัญหาที่เกี่ยวกับการดูแลรักษา เพจที่ถูกสร้างขึ้นโดย Servlet ลักษณะนี้จะยากต่อการดูแลและเปลี่ยนแปลงเป็นอย่างมาก เมื่อใดก็ตามที่เวปไซด์มีความจำเป็นในการที่จะต้องเปลี่ยนหน้าตา นักพัฒนาก็มักจะปวดหัวกับการแก้ไข html โค๊ดต่าง ๆ เสมอ
ปัญหาสุดท้ายที่อาจจะไม่เห็นเด่นชัดมากนักแต่มักเป็นปัญหาสำคัญที่มักเกิดขึ้นในโครงการที่มีขนาดใหญ่คือ ปัญหาการขาดการพัฒนาโครงการอย่างเป็นระบบ สำหรับระบบใหญ่ ๆ แล้ว html เพจต่าง ๆ มักจะถูกสร้างขึ้นโดยกลุ่มบุคคลสามกลุ่มใหญ่ ๆ คือ นักพัฒนาจาว่า (Java Developer), นักพัฒนาเวป (Web Developer) และนักออกแบบเวป (Web Designer)
- นักพัฒนาจาว่า คือบุคคลที่มีหน้าที่รับผิดชอบเกี่ยวกับการเขียนโค๊ดสำหรับส่วนที่เป็น Server Side โดยเฉพาะซึ่งโดยทั่วไปมักจะเป็นการเขียน Servlet, JSP, JDBC หรือ EJB
- นักพัฒนาเวป คือบุคคลที่อยู่กึ่งกลางระหว่างนักพัฒนาจาว่าและนักออกแบบเวปซึ่งหน้าที่ส่วนมากมักจะเป็นการเขียน html ให้ตรงกับ design หรือ layout ที่ได้มาจากนักออกแบบเวปรวมไปถึงการเขียน Script ที่ใช้รันที่ web browser (Client Side) ยกตัวอย่างเช่น JavaScript หรือ VBScript เป็นต้น
- นักออกแบบเวป คือบุคคลที่ทำหน้าที่ในการออกแบบหน้าตาของ html เพจต่าง ๆ ให้ออกมาตามคอนเซ็ปของเวปไซด์นั้น ๆ ซึ่งสำหรับระบบเล็ก ๆ แล้วนักออกแบบเวปมักจะทำหน้าที่เป็นนักพัฒนาเวปไปในตัวอีกด้วย 
การใส่ html โค๊ดเข้าไปใน java จะทำให้การทำงานของกลุ่มคนสามกลุ่มนี้มีความล่าช้า เพราะนักพัฒนาจาว่าจะต้องรอจนกระทั่งนักออกแบบเวปทำการออกแบบเพจจนเสร็จ ตลอดจนรอกระทั่งนักพัฒนาเวปทำการเขียน html ให้เสร็จก่อนที่ตัวเองจะนำ html โค๊ดที่เขียนเสร็จแล้วมาใส่ใน Servlet ซึ่งหลายคนอาจจะคิดว่าทำไมนักพัฒนาจาว่าถึงไม่ทำการเขียนเพจแบบง่าย ๆ ใส่เข้าไปใน Servlet ก่อน คำตอบก็คือหลักจากที่ html ที่เป็นตัวจริงถูกเขียนเสร็จแล้ว นักพัฒนาจาว่าก็จะต้องเสียเวลาอีกครั้งหนึ่งในการใส่ html อันใหม่นี้เข้าไปใน java โค๊ดแทน
จากปัญหาข้างต้นที่เกิดขึ้นเสมอ ๆ ในการพัฒนาเวปไซด์ วงการ Server Side Application จึงได้มีการคิดค้นเทคโนโลยีต่าง ๆ ขึ้นมาเพื่อแก้ไขปัญหาเหล่านี้ เทคโนโลยีอันหนึ่งที่ถูกสร้างขึ้นและนิยมใช้กันมากในกลุ่มนักพัฒนา Servlet คือเทคโนโลยีที่เรียกว่า Template Engine
Template Engine
Template Engine เป็นคอนเซ็ปง่าย ๆ ที่นักพัฒนา(จาว่า)นิยมใช้ในการแยกโค๊ดส่วน html ซึ่งมักจะถูกเรียกว่า Presentation Layer ออกจากส่วนที่เป็นข้อมูล โดยตัว Servlet จะเป็นแต่เพียงตัวที่ใช้ในการคำนวณและสร้างข้อมูลต่าง ๆ ขึ้นมา แล้วให้ส่วนที่เรียกว่า Template Engine นี้ทำการแปะข้อมูลต่าง ๆ ใส่เข้าไปใน html เพจแทน
Template Engine เป็นตัวช่วยในการแก้ไขปัญหาต่าง ๆ ที่เกิดขึ้นจากการรวม html โค๊ดเข้ากับส่วนที่ java โค๊ดได้อย่างมีประสิทธิภาพ นักพัฒนาสามารถทำการพัฒนาระบบไปพร้อม ๆ กับการที่นักออกแบบเวปทำการพัฒนาหน้าตาของเพจ โดยระหว่างที่นักออกแบบเวปทำการออกแบบเพจ นักพัฒนาก็จะทำการสร้างเพจปลอม ๆ ขึ้นมาแล้วใช้เพจนั้นเทสระบบที่ตนเองเขียนขึ้น ซึ่งหลังจากที่นักพัฒนาเวปทำการเขียน html โค๊ดเสร็จแล้ว นักพัฒนาก็จะนำข้อมูล*ที่ถูกใส่ไว้ในเพจปลอม ๆ นั้นไปใส่ไว้ใน html เพจที่ถูกเขียนขึ้นโดยนักออกแบบเวปแทน
* ข้อมูลในที่นี้หมายถึง ตัว token (ยกตัวอย่างเช่น $firstName) ที่ใช้แทนข้อมูลที่จะถูกแทนที่ด้วยข้อมูลจริง(ที่ถูกสร้างขึ้นโดย Servlet) ในระหว่างการแปะข้อมูลเข้ากับ html เพจโดย Template Engine
ส่วนประกอบที่สำคัญสำหรับ Servlet ที่ใช้ใน web application มักจะประกอบด้วย 3 ส่วนใหญ่ ๆ คือ
1. ส่วนข้อมูลกลาง ส่วนนี้เป็นส่วนที่ Servlet ใช้ในการเก็บข้อมูลที่ได้มาจากที่ต่าง ๆ เช่น เดต้าเบสหรือ HttpSession ซึ่งส่วนข้อมูลกลางนี้จะเก็บค่าต่าง ๆ ที่เป็นส่วนประกอบสำคัญในการสร้าง html เพจ อย่างไรก็ตามเรามักจะไม่เห็นส่วนข้อมูลกลางนี้เด่นชัดนักใน web application ทั่ว ๆ ไป เพราะนักพัฒนานิยมนำค่าต่าง ๆ ที่ได้จากเดต้าเบสหรือ HttpSession ไปรวมกับ content ของ html เพจโดยตรงแทนที่จะรวบรวมค่าทั้งหมดที่ต้องการมาเก็บไว้ในส่วนข้อมูลกลางแล้วทำการสร้าง html เพจในภายหลัง
2. ส่วนประมวลผล ส่วนนี้เป็นส่วนที่รับ request จาก client เพื่อนำมาอ่านค่า parameter ต่าง ๆ แล้วทำการประมวลผล โดยอาจมีการติดต่อสื่อสารกับส่วนข้อมูลกลางเพื่อทำการเปลี่ยนแปลงข้อมูลต่าง ๆ ด้วย
3. ส่วนที่ทำหน้าที่ในการสร้างเพจ ส่วนนี้เป็นส่วนที่ทำการสร้าง html เพจออกมาเพื่อส่งกลับไปยัง client โดยข้อมูลต่าง ๆ ที่อยู่ในเพจมักจะเป็นข้อมูลที่ถูกนำมาจากส่วนข้อมูลกลาง
Simple Design
สำหรับส่วนประกอบของ Servlet ทั้ง 3 ส่วนที่กล่าวมาข้างต้น ส่วนที่่น่าสนใจที่สุดเห็นจะเป็นส่วนที่สามซึ่งเป็นส่วนที่ทำหน้าที่ในการสร้างเพจ โดยสำหรับ Servlet ที่ใช้คอนเซ็ปของ Template Engine แล้วส่วนนี้ก็คือส่วนที่ทำหน้าที่เป็น Template Engine นั่นเอง วิธีการแปะข้อมูลต่าง ๆ จากส่วนข้อมูลกลางลงไปยัง html เพจโดย Template Engine นั้นมีหลายวิธี แต่วิธีการที่ผู้เขียนจะกล่าวถึงในวันนี้เป็นวิธีที่ง่ายที่สุดซึ่งเราเรียกวิธีนี้ว่า การแทนที่ String ด้วย String
วิธีการแทนที่ String ด้วย String นี้มีหลักการง่าย ๆ คือขั้นแรกเราจะต้องทำการออกแบบรูปแบบของ String ขึ้นมาลักษณะหนึ่งโดยเมื่อไรก็ตามที่เราเจอ String รูปแบบนี้เราก็จะเข้าใจว่าค่าที่อยู่ข้างในของ String นี้เป็นเพียงชื่อของตัวแปรซึ่งเราจะต้องทำการหาค่าของตัวแปรนี้จากที่ใดที่หนึ่งมาใส่แทน
หลักการข้างต้นเมื่อถูกนำมาประยุกต์ใช้กับ Servlet รูปแบบของ String ที่เราออกแบบก็จะกลายเป็นตัวแปรต่าง ๆ ที่เราใส่ไว้ใน html template โดยเราจะให้ Template Engine ทำการหาตัวแปรเหล่านี้จาก html template ดังกล่าว ซึ่งหลังจากนั้น Template Engine ก็จะทำการหาค่าที่แมชกับชื่อของตัวแปรเหล่านี้จากที่ต่าง ๆ ใส่เข้าไปใน html template แทน โดยทั่วไปเรามักนิยมให้ Template Engine ทำการหาค่าจริงของตัวแปรต่าง ๆ ที่ถูกพบใน html template จากที่ ๆ เดียวซึ่งก็คือส่วนข้อมูลกลางนั่นเอง ท้ายที่สุดผลที่ออกมาจากการแปะค่าต่าง ๆ จากส่วนข้อมูลกลางเข้าไปยัง html template ก็จะกลายเป็น html เพจที่สมบูรณ์และพร้อมที่จะถูกส่งกลับไปยัง client
สมมุติว่าเราให้ส่วนข้อมูลกลางของเราเป็น Hashtable โดยเราเก็บชื่อและค่าของตัวแปรต่าง ๆ ไว้ข้างในดังตัวอย่างข้างล่าง

key="firstName", value="Paul"
key="lastName", value="Clare"
key="sex", value="male"
key="position", value="Director of R&D"
ถ้าเรานิยามรูปแบบของ String ขึ้นมาโดยใช้ <! เป็น prefix และ > เป็น suffix ดังนั้น <!variable1> ก็จะหมายถึงตัวแปรตัวหนึ่งที่ชื่อ variable1 ซึ่งทาง Template Engine จะต้องมีหน้าที่ในการหาค่าของตัวแปรนี้มาใส่แทนส่วนที่เป็น <!variable1> โดยตัวอย่างของ html template ที่เราใช้ในการนำค่าต่าง ๆ ที่เก็บอยู่ในส่วนข้อมูลกลางข้างต้นก็อาจจะเป็นอย่าง html ข้างล่างนี้

<html>
<head>
<title>Simple String Token</title>
</head>
<body>
My firstname is: <!firstName> <br>
My lastname is: <!lastName> <br>
My sex is: <!sex> <br>
My position is: <!position> <br>
<body>
</html>
ถ้าเราให้ Template Engine ทำการแมปค่าต่าง ๆ ที่อยู่ใน Hashtable ข้างต้นกับตัว html template ตัว html เพจหลังสุดก็จะออกมาเป็น

<html>
<head>
<title>Simple String Token</title>
</head>
<body>
My firstname is: paul <br>
My lastname is: clare <br>
My sex is: male <br>
My position is: Director of R&D <br>
<body>
</html>
คลาสง่าย ๆ ที่เราใช้ในการแปะค่าต่าง ๆ ที่อยู่ใน Hashtable เข้าไปยัง html template ที่เป็น String ก็อาจจะเป็นอย่างคลาสนี้ StringUtil.java
คลาส StringUtil มี method อยู่เพียงสอง method คือ
public static String replace(String source, String token, String newValue);
public static String replace(String source, Hashtable nvPairs, String tokenPrefix, String tokenSuffix);
ตัว method แรกใช้สำหรับเปลี่ยนสตริง token ที่ปะปนอยู่ในสตริง source ให้กลายเป็นสตริง newValue ยกตัวอย่างเช่น
String source = "I'm borg. Resistance is futile!!!  borg! borg! borg!";
String token = "borg";
String newValue = "boy";
ผลที่ได้หลังจากการใส่ค่าทั้งสามลงไปยัง method นี้ก็จะเป็น
"i'm boy. Resistance is futile!!! boy! boy! boy!"
ตัว method ที่สองเป็นส่วนที่เราจะใช้เป็นส่วนประกอบสำหรับสร้าง Template Engine ของเราขึ้นมาโดย method นี้มีหน้าที่ในการนำค่าต่าง ๆ ที่อยู่ใน Hashtable ที่ชื่อ nvPairs มาแทนค่าต่าง ๆ ที่อยู่ในสตริง source โดยค่าต่าง ๆ ที่อยู่ในสตริง source นี้จะต้องมีชื่ออยู่ในคีย์ลิสของ nvPairs โดยจะมี prefix เป็นสตริง tokenPrefix และมี suffix เป็น tokenSuffix ยกตัวอย่างเช่น
String source = "I'm <!species>.  Resistance is <!result>!!! <!species>! <!species>! <!species>!";
Hashtable nvPairs = new Hashtable();
nvPairs.put("species", "borg");
nvPairs.put("result", "futile");
String tokenPrefix = "<!";
String tokenSuffix = ">";
ถ้าเรานำค่าต่าง ๆ เหล่านี้ใส่ไปยัง method ที่สอง ผลก็จะออกมาเป็น
"i'm borg. Resistance is futile!!! borg! borg! borg!"
วิธีการแทนที่ String ด้วย String นี้เป็นวิธีการที่ง่ายที่สุดในการสร้าง Template Engine แต่วิธีการที่นิยมกันมากมักเป็นวิธีการแปลงสตริงออกมาเป็น token หลากหลายชนิดโดยอาศัย Parser ที่ถูกสร้างขึ้นเป็นพิเศษเพื่อรองรับรูปแบบของสตริงที่ถูกนิยามขึ้นมาสำหรับ Template Engine นั้น ๆ โดยเฉพาะซึ่งสำหรับผู้อ่านที่สนใจสามารถศึกษารายอะเอียดเพิ่มเติมได้จาก Resources ที่อยู่ในส่วนท้ายสุดของบทความนี้
TemplateEngineDemo
จากหลักการของ Template Engine ข้างต้น เรามาดูตัวอย่างของ Servlet (TemplateEngineDemo.java) ที่ถูกประยุกต์ใช้สำหรับ Template Engine กันหน่อยดีกว่า

...
public class TemplateEngineDemo extends HttpServlet {
  private String documentPath = null;
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    documentPath = getInitParameter("documentPath");
    // if no directory provided for loading the template, throwing the exception
    if (documentPath == null || documentPath.trim().length() == 0) {
      throw new UnavailableException (this, "No directory provided for loading the template.");
    }
  }
...

โค๊ดส่วนแรกที่เราจะพูดถึงคือส่วนที่อยู่บนสุดของคลาส TemplateEngineDemo โดยขั้นแรกเราจะเห็นว่าคลาสนี้ได้ทำการ extend คลาส HttpServlet เพื่อให้กลายเป็น Servlet คลาสนี้มี instance variable ที่เป็นสตริงตัวหนึ่งชื่อ documentPath เราใช้สตริงตัวนี้สำหรับเก็บค่าของ directory ที่ตัว TemplateEngineDemo ใช้สำหรับอ้างถึง directory ที่จะใช้โหลด html template ต่าง ๆ
ถัดมาในส่วนของ Servlet Initialization คือฟังก์ชัน init(...) ส่วนนี้มีการอ่านค่าของ documentPath ที่มาจาก parameter ที่เราใส่เข้าไปใน environment ของ Servlet โดยถ้าเราลืมกำหนดค่านี้หรือค่าที่ถูกใส่เข้าไปนี้มีความยาวเท่ากับศูนย์ ตัว Servlet นี้ก็จะหยุดทำงานด้วยการ throw ตัว UnavailableException ออกมา
private String getDocumentPath() {
    return documentPath;
  }
...
private final String loadTemplate(String fileName) throws IOException {
    String templatePath = getDocumentPath();
    File tmplFile = new File(templatePath + fileName);
    BufferedReader in = new BufferedReader(new FileReader(tmplFile));
    String data = null;
    StringBuffer buf = new StringBuffer();
    while ( (data = in.readLine()) != null ) {
      buf.append(data);
    }
    return buf.toString();
  }
...

โค๊ดข้างบนเป็นส่วนที่ TemplateEngineDemo ใช้ในการโหลด html template เข้ามาอยู่ในรูปของสตริงโดยอาศัยฟังก์ชันที่ชื่อ loadTemplate(String fileName) โดย directory ที่ฟังก์ชันนี้ใช้จะถูกนำมาจาก private ฟังก์ชันที่ชื่อ getDocumentPath() ซึ่งจะคืนค่าของ documentPath ที่ถูกเซ็ตค่าแล้วในช่วง Servlet Initialization
...
public void service(HttpServletRequest req, HttpServletResponse res)
                throws IOException, ServletException {
      long userId = -1;
      String userIdStr = req.getParameter("userId");
      if (userIdStr != null) {
        try {
          userId = Long.parseLong(userIdStr);
        } catch (NumberFormatException nfe) {
          // so userId = -1;
        }
      }
      // find a user and load his information into a hashtable
      User user = User.findUserById(userId);
      Hashtable nvPairs = new Hashtable();
      nvPairs.put("firstName", user.getFirstName());
      nvPairs.put("lastName", user.getLastName());
      nvPairs.put("sex", user.getSex());
      nvPairs.put("position", user.getPosition());
      // load a template and map the information of user into it
      String template = loadTemplate("userInfo.html");
      String result = StringUtil.replace(template, nvPairs, "<!", ">");
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      out.println(result);
      out.close();
  }
...
ฟังก์ชันท้ายสุดที่เป็นหัวใจของ Servlet นี้ก็คือ service() คลาส TemplateEngineDemo ทำการ override ฟังก์ชันนี้ซึ่งเป็นฟังก์ชันที่อยู่ในคลาส HttpServlet เพื่อทำการรวม request ที่มาจาก HTTP GET และ HTTP POST เข้าด้วยกันซึ่งโดยปกติ request สองอันนี้จะถูก handle โดยฟังก์ชัน doGet() และ doPost() ตามลำดับ
หน้าที่แรกสุดของฟังก์ชัน service() นี้คือการอ่านค่าของ userId ที่มาจาก request โดยจะแปลงค่าจาก String ให้กลายเป็น long ซึ่งถ้าค่าดังกล่าวไม่มีอยู่ใน request ค่าของ userId จะกลายเป็น -1 ซึ่งเป็นค่า default โดยอัตโนมัติ ต่อมาค่านี้จะถูกส่งผ่านเข้าไปยัง static ฟังก์ชัน User.findUserById(long userId) ซึ่งเป็นฟังก์ชันหนึ่งที่อยู่ในคลาส User (User.java) โดยคลาสนี้จะเป็นคลาสหลอก ๆ ที่ทำหน้าที่เป็นเสมือนออฟเจคที่เราใช้ในการติดต่อกับเดต้าเบสหรือ Entity Bean เพื่อหา User ที่เราต้องการ โดยผลที่ได้กลับคืนมาจากฟังก์ชัน User.findUserById(...) ก็คือ User ออฟเจคที่มีค่าของ userId ตรงกับ userId ที่ถูกใส่เข้าไปในฟังก์ชันนั้นนั่นเอง หลักจากที่ได้ User ออฟเจคออกมาแล้ว ตัว Servlet ก็จะทำการใส่ค่าต่าง ๆ ของ User นั้นเข้าไปยังส่วนข้อมูลกลางซึ่งในกรณีของเราก็คือ Hashtable ที่ชื่อ nvPairs
หลังจากที่เราใส่ข้อมูลทั้งหมดเข้าไปยังส่วนข้อมูลกลาง Template Engine พระเอกของเราก็ออกมาทำหน้าที่ในการแปะค่าต่าง ๆ ที่อยู่ในส่วนข้อมูลกลางเข้าไปยัง html template ซึ่งโค๊ดที่ทำหน้าที่ดังกล่าวก็คือ
     ...
      // load a template and map the information of user into it
      String template = loadTemplate("userInfo.html");
      String result = StringUtil.replace(template, nvPairs, "<!", ">");
     ...
จากโค๊ดเราจะเห็นว่าขั้นแรกเราจะทำการโหลด html template ที่ชื่อ userInfo.html ขึ้นมาโดยผ่านทางฟังก์ชันที่ชื่อ loadTemplate(...) เสร็จแล้วทำการแมปค่าต่าง ๆ ที่อยู่ในส่วนข้อมูลกลางหรือ nvPairs นี้เข้าไปยัง html template นี้โดยผ่าน static ฟังก์ชันที่ชื่อ StringUtil.replace(...) โดยใช้ <! เป็น prefix และ > เป็น suffix สำหรับค่าตัวแปรต่าง ๆ ที่อยู่ใน userInfo.html ตามลำดับ
เมื่อ Template Engine ทำงานเสร็จแล้วค่าสตริงท้ายสุดที่ออกมาก็คือ html ที่สมบูรณ์ พร้อมที่จะถูกส่งกลับไปยัง client (web browser) ซึ่ง TemplateEngineDemo ก็จะทำการเซ็ต HTTP header, เรียก PrintWriter เพื่อทำการ print ตัวสตริง result ออกไปดัง code snippet ข้างล่าง
     ...
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      out.println(result);
      out.close();
     ...
เราจะเห็นว่าโค๊ดทั้งหมดของ TemplateEngineDemo จะไม่มีส่วนที่เป็น html ปะปนอยู่เลย ดังนั้นในกรณีที่เราต้องการเปลี่ยนหน้าตาของ userInfo.html เราก็เพียงทำการเปลี่ยน html layout ที่อยู่ในไฟล์นั้นโดยยังคงเก็บชื่อของตัวแปรต่าง ๆ ที่อยู่ใน <!XXX> ไว้ก็เป็นอันเสร็จวิธี
ผู้อ่านสามารถทำการรัน TemplateEngineDemo Servlet ได้ด้วยตนเอง โดยดาวโหลดไฟล์ที่เกี่ยวข้องทั้งหมดจาก TemplateEngine.zip
 Note: สำหรับผู้อ่านที่ใช้ Tomcat เป็น Servlet Engine หลังจากที่ทำการติดตั้ง TemplateEngineDemo เข้าไปยัง working directory แล้ว ให้ทำการเพิ่มส่วนนี้เข้าไปยังไฟล์ web.xml ของ working directory นั้น ยกตัวอย่างเช่น
<web-app>
...
 <servlet>
  <servlet-name>TemplateEngineDemo</servlet-name>
  <servlet-class>com.jarticles.TemplateEngineDemo</servlet-class>
  <init-param>
   <param-name>documentPath</param-name>
   <param-value>D:/MyWebApplication/template/</param-value>
  </init-param>
 </servlet>
 ...
</web-app>
โดย documentPath ในที่นี้เราอ้างถึง directory ที่ชื่อ D:/MyWebApplication/template/ ซึ่งบรรจุไฟล์ userInfo.html เอาไว้
ในการรันทดสอบ ให้ใช้ url ที่มี parameter ที่ชื่อ userId ใส่รวมเข้าไปด้วย ยกตัวอย่างเช่น
http://serverName:port/contextPath/TemplateEngineDemo?userId=0
http://serverName:port/contextPath/TemplateEngineDemo?userId=1
http://serverName:port/contextPath/TemplateEngineDemo?userId=2 เป็นต้น
Template Engine Framework
โดยทั่วไป Template Engine ต่าง ๆ มักถูกเขียนขึ้นในลักษณะของ framwork โดยเราสามารถใช้ framework นี้สร้าง web application ต่าง ๆ ขึ้นมาตามแต่จุดประสงค์ของงานที่เราต้องการได้ ผู้เขียนจะขอยกตัวอย่างวิธีการทำ framework ของ Template Engine แบบง่าย ๆ โดยใช้โค๊ดจาก TemplateEngineDemo ที่เราพูดถึงมาแล้วข้างต้นเป็นแบบอย่าง
framework โดยทั่วไปมักจะเป็นสิ่งที่อำนวยความสะดวกสำหรับ web application โดยจะถูกออกแบบมาให้ตัว web application สนใจกับสิ่งที่ตนเองสมควรจะทำแต่เพียงอย่างเดียวซึ่งสำหรับ Template Engine แล้วหน้าที่หลัก ๆ ของ framework นี้ก็จะเป็น
1. โหลด html template เข้ามายังระบบ
2. ทำการอ่านค่าต่าง ๆ จากส่วนข้อมูลกลางมาแปะเข้ากับ html template ที่ถูกโหลดเข้ามาไว้ก่อนแล้ว
3. ทำการส่งผลท้ายสุดกลับไปยัง web browser
สำหรับ web application ที่ถูกสร้างขึ้นบน framwork ข้างต้น สิ่งที่ต้องทำก็มีเพียง
1. อ่าน request ที่มาจาก web browser
2. ประมวลผล request ที่ได้มา
3. รวบรวมผลที่ได้ใส่เข้าไปยังส่วนข้อมูลกลางเพื่อให้ framework ทำการแปะข้อมูลต่าง ๆ เข้าไปยัง html template แล้วส่งผลสุดท้ายกลับไปยัง web browser
ถ้าเราต้องการทำ TemplateEngineDemo ให้เป็นลักษณะ Template Engine Framework เราก็สามารถแยกส่วนออกเป็น 2 Layers ด้วยกันคือส่วนที่เป็น Framework Layer และส่วนที่เป็น Appliation Layer โดยขั้นแรกเราจะพูดถึงส่วนที่เป็น Framework Layer กันก่อน
// TemplateBaseServlet.java (Framework Layer)
package com.jarticles;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public abstract class TemplateBaseServlet extends HttpServlet {
  protected static final String DEFAULT_TOKEN_PREFIX = "<!";
  protected static final String DEFAULT_TOKEN_SUFFIX = ">";
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
  }
  /**
   * implemented by subclass
   */
  protected abstract String getDocumentPath();
  /**
   * the value of tokenPrefix can be changed if subclass override this method
   */
  protected String getTokenPrefix() {
    return DEFAULT_TOKEN_PREFIX;
  }
  /**
   * the value of tokenSuffix can be changed if subclass override this method
   */
  protected String getTokenSuffix() {
    return DEFAULT_TOKEN_SUFFIX;
  }
  /**
   * use this method if you want to set the content type and header yourself
   */
  protected final String getPageContent(String templateName, Hashtable nvPairs)
                           throws IOException {
    String template = loadTemplate(templateName);
    return StringUtil.replace(template, nvPairs, getTokenPrefix(), getTokenSuffix());
  }
  /**
   * use this method if you want TemplateBaseServlet sending the outputStream to the client for you
   */
  protected final void sendPage(HttpServletResponse res, String templateName, Hashtable nvPairs)
                         throws IOException, ServletException {
    String content = getPageContent(templateName, nvPairs);
    res.setContentType("text/html");
    res.setHeader("pragma", "no-cache");
    PrintWriter out = new PrintWriter(res.getOutputStream());
    out.print(content);
    out.close();
    return;
  }
  private final String loadTemplate(String fileName) throws IOException {
    String templatePath = getDocumentPath();
    File tmplFile = new File(templatePath + fileName);
    BufferedReader in = new BufferedReader(new FileReader(tmplFile));
    String data = null;
    StringBuffer buf = new StringBuffer();
    while ( (data = in.readLine()) != null ) {
      buf.append(data);
    }
    return buf.toString();
  }
}
คลาส TemplateBaseServlet เป็นคลาสที่ถูกออกแบบมาเพื่อใช้สำหรับเป็น Framework Layer ให้กับคลาส Servlet อื่นที่ต้องการพัฒนาระบบโดยใช้หลักการของ Template Engine โดยคลาส Servlet เหล่านั้นจะต้องทำการ subclass คลาส TemplateBaseServlet เพื่อที่จะสามารถเรียกใช้ฟังก์ชันต่าง ๆ ที่ทางคลาสนี้จัดเตรียมไว้ให้ ถ้าเราสังเกตดี ๆ เราจะเห็นว่าคลาส TemplateBaseServlet เป็นคลาส abstract โดยฟังก์ชันที่ทาง subclass จะต้องทำการ implement ก็คือฟังก์ชัน getDocumentPath() หลายคนอาจจะเดาได้ว่าทำไม subclass ถึงต้องทำการ implement ฟังก์ชันนี้ เหตุผลก็เพราะว่า Servlet แต่ละตัวที่มา subclass คลาส TemplateBaseServlet มักจะมี working directory ที่ใช้เก็บ html template ต่าง ๆ เป็นของตัวเอง ดังนั้น subclass เหล่านั้นก็ควรที่จะทำการอ้างถึง directory ที่ตนใช้ด้วยตนเองซึ่งทาง TemplateBaseServlet ก็จะใช้ค่าที่ได้จากฟังก์ชันที่ถูก implement โดย subclass นี้โหลด html template ต่าง ๆ โดยผ่านทาง private ฟังก์ชันที่ชื่อ loadTemplate(String fileName) (สำหรับวิธีการให้ subclass ทำการ implement ฟังก์ชันต่าง ๆ ด้วยตัวเองโดยตัว base class จะเรียกใช้ฟังก์ชันเหล่านั้นโดยไม่สนใจว่าส่วนที่เป็น implementation จะเป็นอย่างไร เรามักจะเรียกวิธีการเช่นนี้ว่า Template Method)
นอกจากฟังก์ชัน getDocumentPath() ที่ทาง subclass สามารถทำการ override ได้แล้ว ทางคลาส TemplateBaseServlet ก็ยังมีอีกสองฟังก์ชันที่อนุญาติให้ทาง subclass ทำการ overrdie ได้อีกคือฟังก์ชัน getTokenPrefix() และ getTokenSuffix()
เราจะเห็นว่าฟังก์ชันสองอันนี้จะคืนค่าที่เราใช้เป็นตัว prefix และ suffix สำหรับสตริงที่เราใส่ไว้ใน html template ซึ่งสตริงเหล่านี้จะถูกหาโดย Template Engine แล้วทำการแมปสตริงเหล่านี้เข้ากับชื่อของเดต้าที่เราใส่ไว้ในส่วนของข้อมูลกลาง โดยค่า default ก็จะเป็น "<!" และ ">" ตามลำดับ อย่างไรก็ตามถ้าทาง subclass ต้องการที่จะเปลี่ยนตัว prefix และ suffix นี้ก็สามารถทำได้โดยการ override ฟังก์ชันสองตัวนี้โดยทำการคืนค่าที่เป็นอย่างอื่นแทน ยกตัวอย่างเช่น
public class TemplateBaseSubclass extends TemplateBaseServlet {
...
public String getDocumentPath() {
  return documentPath;
}
protected String getTokenPrefix() {
    return "<--$";
  }
  protected String getTokenSuffix() {
    return "-->";
  }
...
อย่างที่กล่าวมาข้างต้นว่าส่วนที่เป็น framework จะต้องทำหน้าที่ในการแปะเดต้าที่อยู่ในส่วนข้อมูลกลางเข้ากับ html template ที่ถูกโหลดขึ้นมา ซึ่งสำหรับ TemplateBaseServlet แล้วฟังก์ชันที่ทำหน้าที่ดังกล่าวก็คือฟังก์ชัน getPageContent(...) และ sendPage(...) โดยฟังก์ชันแรกเป็นฟังก์ชันที่ใช้สำหรับงานทั่ว ๆ ไปซึ่งจะทำแต่เพียงการแปะเดต้าที่อยู่ในส่วนข้อมูลกลางเข้ากับ html template แล้วคืนค่าของ html ที่สมบูรณ์แบบออกมา ฟังก์ชันที่สองเป็นฟังก์ชันที่ทำทุกอย่างให้กับ subclass ซึ่งสำหรับ subclass ที่เป็น Servlet แล้ว สิ่งสุดท้ายที่จะต้องทำก็คือการส่ง html กลับไปยัง web browser ดัง code snippet ข้างล่าง
...
protected final void sendPage(HttpServletResponse res, String templateName, Hashtable nvPairs)
                         throws IOException, ServletException {
    String content = getPageContent(templateName, nvPairs);
    res.setContentType("text/html");
    res.setHeader("pragma", "no-cache");
    PrintWriter out = new PrintWriter(res.getOutputStream());
    out.print(content);
    out.close();
    return;
  }
...
เรามาดูวิธีการเขียน Servlet (หรือ Application Layer) ที่สร้างอยู่บน Framework Layer ซึ่งในที่นี้ก็คือการ subclass คลาส TemplateBaseServlet กันบ้าง
// MyTemplateServlet.java (Application Layer)
package com.jarticles;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyTemplateServlet extends TemplateBaseServlet {
  private String documentPath = null;
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    documentPath = getInitParameter("documentPath");
    // if no directory provided for loading the template, throwing the exception
    if (documentPath == null || documentPath.trim().length() == 0) {
      throw new UnavailableException (this, "No directory provided for loading the template.");
    }
  }
  /**
   * now, implemented by this class
   */
  public String getDocumentPath() {
    return documentPath;
  }

  public void service(HttpServletRequest req, HttpServletResponse res)
                throws IOException, ServletException {
      long userId = -1;
      String userIdStr = req.getParameter("userId");
      if (userIdStr != null) {
        try {
          userId = Long.parseLong(userIdStr);
        } catch (NumberFormatException nfe) {
          // so, userId = -1;
        }
      }
      // find a user and load his information into a hashtable
      User user = User.findUserById(userId);
      Hashtable nvPairs = new Hashtable();
      nvPairs.put("firstName", user.getFirstName());
      nvPairs.put("lastName", user.getLastName());
      nvPairs.put("sex", user.getSex());
      nvPairs.put("position", user.getPosition());
      // send nvPairs to map with userInfo.html template and print
      // the outputStream to the client
      sendPage(res, nvPairs, "userInfo.html");
  }
}
เราจะเห็นว่าโค๊ดส่วนใหญ่ที่อยู่ในคลาสนี้ก็คือ โค๊ดในส่วนของฟังก์ชัน service(...) จากคลาส TemplateEngineDemo นั่นเอง คลาสนี้จะไม่มีส่วนที่ทำหน้าที่เกี่ยวกับ Presentation (HTML) อยู่เลย โดยหน้าที่หลัก ๆ ของคลาสนี้ก็มีแต่เพียงการอ่าน request ที่มาจาก web browser นำค่าที่ได้ไปหา User ออฟเจคที่อยู่ในเดต้าเบสเสร็จแล้วใส่ข้อมูลที่เกี่ยวข้องของ User เช่น firstName, lastName, ฯลฯ ที่ได้เข้าไปยังส่วนข้อมูลกลางซึ่งในที่นี้ก็คือ Hashtable ที่ชื่อ nvPairs ก่อนที่จะเรียกฟังก์ชัน sendPage(...) ซึ่งเป็นฟังก์ชันที่ inherit มาจาก base class โดยจะทำหน้าที่เป็น Template Engine สำหรับแมปเดต้าที่อยู่ในส่วนข้อมูลกลางเข้ากับ html template ที่ชื่อ userinfo.html แล้วส่ง outputStream กลับไปยัง web browser ในท้ายสุด
ผู้อ่านสามารถทำการรัน MyTemplateServlet Servlet ได้ด้วยตนเอง โดยดาวโหลดไฟล์ที่เกี่ยวข้องทั้งหมดจาก TemplateEngine.zip
Comments
คลาส TemplateBaseServlet เป็นเพียงตัวอย่างที่ถูกยกขึ้นมาใช้เพื่อให้ผู้อ่านเข้าใจถึงหลักการทำงานขั้นพื้นฐานของ Template Engine โดยคลาสนี้สามารถที่จะใช้งานได้จริงกับ web application ที่มีจำนวนเพจไม่มากนักซึ่งข้อจำกัดนี้เกิดขึ้นมาจาก

1. คลาสนี้ใช้หลักการแทนที่สตริงด้วยสตริงสำหรับใส่ค่าต่าง ๆ ที่อยู่ในส่วนข้อมูลกลางเข้าไปยัง html template ซึ่งสำหรับ Server Side Application แล้ววิธีการ implementation แบบนี้ใช้ CPU time ค่อนข้างมาก

2. เมื่อไรก็ตามที่มี request เข้ามา คลาสนี้จะทำการโหลด html template ที่เกี่ยวข้องขึ้นมาเพื่อทำการใส่ค่าที่มาจากส่วนข้อมูลกลางเข้าไปทุกครั้ง การโหลด html template ขึ้นมาทุกครั้งนี้ เราจะต้องเสียเวลาส่วนหนึ่งให้กับ I/O ซึ่งวิธีการที่เหมาะสมกว่าคือการ cache ตัว html template ต่าง ๆ ไว้ใน memory หลักจากมีการโหลด html template ดังกล่าวขึ้นมาเป็นครั้งแรก

อย่างไรก็ตามการโหลด html template ทุกครั้งนี้ก็ยังคงเหมาะสำหรับช่วง Development Phase ซึ่งนักพัฒนายังคงทำการเปลี่ยนแปลงและแก้ไขระบบอยู่โดยจะช่วยทำให้ประหยัดเวลาที่เสียไปกับการ restart ตัว Servlet Engine ทุกครั้งที่มีการเปลี่ยนแปลงหน้าตาของ html template ในกรณีที่เราทำการ cache ตัว html template
Note: เราสามารถทำการ turn on/off การ cache ตัว html template ของ Servlet ได้ในช่วง Runtime โดยการเซ็ตค่า on/off ไว้ใน initial properties ของ Servlet ซึ่งเราจะใช้ turn off โหมดในช่วง Development Phase และใช้ turn on โหมดในช่วงที่เราใช้งานจริง(เช่นการรัน Servlet ที่ Production Site) เท่านั้น

3. ในกรณีที่เราต้องการสร้างตารางที่เกิดจากลิสของข้อมูล เรายังคงต้องใส่ html โค๊ดส่วนหนึ่งเข้าไปยัง java โค๊ด ด้วยเหตุที่ว่าคลาส TemplateBaseServlet ไม่ได้ทำการ implement ตัว syntax อื่น ๆ ที่ใช้สำหรับสร้างลูป ยกตัวอย่างเช่น for-each, while เป็นต้น
Wrapping up
มาถึงจุดนี้หลายท่านอาจจะถามว่าการใช้ Servlet ที่ใช้หลักการของ Template Engine กับการใช้ JSP ในการพัฒนา web application เราควรจะเลือกอย่างไหนถึงจะดีกว่ากัน คำถามนี้ยังเป็นคำถามที่ติดอยู่ในใจของนักพัฒนาหลาย ๆ คนซึ่งคำตอบก็มักจะขึ้นอยู่กับประสบการณ์ส่วนตัวของนักพัฒนาแต่ละคนที่มีกับ เทคโนโลยีทั้งสองแบบนี้  สำหรับตัวผู้เขียนเองแล้ว ถ้า web application มีเดต้าเป็นในลักษณะที่ถูกแชร์โดยเพจต่าง ๆ ที่อยู่ในระบบทั้งระบบ Servlet ที่ใช้หลักการของ Template Engine มักเป็นตัวเลือกที่ดีกว่าในการสร้างเพจเพื่อควบคุม flow ต่าง ๆ ที่เกิดขึ้นจากกิจกรรมของผู้ใช้แต่ละคน ในทางกลับกันสำหรับ web application ที่ไม่มีความสัมพันธ์ระหว่างเพจต่อเพจมาก ตลอดจนมีเดต้าที่เป็นลักษณะกระจัดกระจายไม่เกี่ยวข้องกันมากนัก การใช้ JSP ก็มักจะเป็นตัวเลือกที่ดีกว่าเสมอ
นอกจากการใช้ Servlet หรือ JSP แยกกันต่างหากแล้ว ทาง SUN ก็มีข้อแนะนำในการพัฒนา web application โดยการใช้คอนเซ็ปของ MVC (Model-View-Controller) ซึ่งใน J2EE คอนเซ็ปนี้มักถูกเรียกว่า Model 2 ซึ่งสำหรับผู้อ่านที่สนใจสามารถหารายละเอียดเพิ่มเติมได้จาก Understanding JavaServer Pages Model 2 architecture
Resources
ผู้อ่านสามารถดาวโหลด source code ทั้งหมดที่เกี่ยวข้องกับบทความนี้ได้จาก TemplateEngine.zip
สำหรับผู้อ่านที่สนใจเกี่ยวกับคอบเซ็ปของ Template Engine แต่ไม่ชอบ TemplateBaseServlet ของผู้เขียน ; ) สามารถศึกษาเพิ่มเติมและทดลองใช้เทคโนโลยีนี้ได้จาก
- WebMacro : เทคโนโลยีแบบ Template Engine ที่มักถูกพูดถึงเสมอเวลาที่มีการถกเถียงกันเรื่องข้อดีข้อเสียของ JSP เทคโนโลยี
- VelocityTemplate Engine ของ Apache ที่เพิ่งออก v1.0 Released มาเมื่อไม่นานมานี้
- FreeMaker : Open-source Template Engine ที่ถูกออกแบบมาเพื่อใช้กับ Servlet โดยเฉพาะ

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
 

Java Servlet Copyright © 2012 | Design by I Love Linux