นี่เป็นโครงการที่สร้างขึ้นเป็นส่วนหนึ่งของหลักสูตรที่ฉันเรียนที่มหาวิทยาลัยวอร์ซอว์ มันเป็นการใช้งานอย่างง่าย ๆ ของคอนเทนเนอร์ servlet ที่รองรับ Java Servlets ที่สอดคล้องกับ Java Servlet API แม้ว่ามันจะค่อนข้างเปลือยเปล่า แต่ก็สามารถโหลดและเรียกใช้แอพพลิเคชั่นง่าย ๆ ที่ใช้ฟังก์ชันพื้นฐานของ Servlets รวมถึงการส่ง JSP และ Async Servlets
ฉันไม่พบการใช้งานขนาดเล็กอื่น ๆ อีกมากมายของคอนเทนเนอร์ servlet ดังนั้นอย่าลังเลที่จะใช้สิ่งนี้เป็นตัวอย่างของวิธีการสร้างคอนเทนเนอร์เช่นนี้ โปรดทราบว่าเนื่องจากข้อ จำกัด ด้านเวลามันเป็นโครงการที่ไม่สมบูรณ์และมีบั๊กและในขณะที่ฉันพยายามที่จะซื่อสัตย์ต่อ Servlet API ฉันได้รับเสรีภาพที่นี่และที่นั่น
แนวคิดก็คือคอนเทนเนอร์นี้ทำงานในลักษณะเดียวกันกับ Tomcat คุณสามารถเพิ่มคลาส servlet โดยใช้ servletContainer :: addRoute แต่วิธีที่ต้องการคือการใช้ฟังก์ชันการสแกนส่วนประกอบ นั่นคือถ้าแอปพลิเคชันถูกซิปลงในไฟล์ warName.war และวางไว้ในไดเรกทอรี deploy ภายใน server/src/main/resources มันจะโหลดบนเซิร์ฟเวอร์เริ่มต้นและพร้อมใช้งานภายใต้ localhost:8000/warName
โดยค่าเริ่มต้นเซิร์ฟเวอร์ทำงานบนพอร์ต 8000 สามารถเปลี่ยนแปลงได้ในฟังก์ชัน Main ฟังก์ชั่นนี้ยังมีและตัวอย่างของวิธีการเริ่มต้นและกำหนดค่าเซิร์ฟเวอร์นี้
โครงการถูกสร้างขึ้นโดยใช้ Gradle มันมีสองโครงการย่อย: เซิร์ฟเวอร์และแอปพลิเคชันตัวอย่าง (ฐานข้อมูลหนังสือง่าย ๆ ) มันถูกทดสอบภายใต้ Linux และฉันไม่รู้ว่ามันทำงานภายใต้ Windows/MacOS หรือไม่
เพื่อเรียกใช้การใช้เซิร์ฟเวอร์:
./gradlew server:run
เซิร์ฟเวอร์เริ่มต้นต้องการให้เราสร้างอินสแตนซ์ของ ServletContainer
ServletContainer(int threads) สร้างอินสแตนซ์ใหม่ของ ServletContainer ด้วยจำนวนเธรดที่กำหนดใช้ในการสร้าง Threadpool พูลนี้จะถูกใช้เพื่อจัดการคำขอ HTTP
void ServletContainer::start(int port) เริ่มคอนเทนเนอร์ servlet บนพอร์ตที่กำหนด โปรดทราบว่าไฟล์. war ไม่ได้โหลด ณ จุดนี้ พวกเขาจะถูกโหลดเฉพาะเมื่อมีการเรียก ServletContainer:servletScan
void ServletContainer::stop()หยุดคอนเทนเนอร์อย่างสง่างาม
void ServletContainer::servletScan() โหลดไฟล์. war ทั้งหมดจาก deploy DIRECTORY ภายใน server/src/main/resources มันจะโหลดไฟล์. jsp เช่นกันและส่งสัญญาณลงในไฟล์. class ทันที จะมีการโหลดคลาสเฉพาะกับ @WebServlet เท่านั้น ข้อแม้หนึ่งไฟล์คือไฟล์. war ที่ฉันใช้มีโครงสร้างไดเรกทอรีเฉพาะที่ฉันไม่แน่ใจว่าเหมือนกับในไฟล์. สงครามอื่น ๆ ส่วนใหญ่ คุณสามารถตรวจสอบงาน Gradle war ในแอปพลิเคชันตัวอย่างเพื่อดูว่าโครงสร้างนี้ควรเป็นอย่างไร วิธีนี้ควรเรียกใช้อย่างมากที่สุดหนึ่งครั้งก่อนเริ่ม () วิธี
void ServletContainer::servletScan(String url) เหมือนกับ ServletContainer::servletScan() แต่สามารถโหลดไฟล์. war จาก URL ที่กำหนด
void ServletContainer::addRoute(Class<? extends HttpServlet> cls, String path)เพิ่ม servlet ลงในคอนเทนเนอร์ Servlet จะจัดการ URL ที่กำหนดเท่านั้น (ดู คำถามที่พบบ่อย สำหรับรายละเอียดเพิ่มเติม)
เซิร์ฟเวอร์รองรับการจัดการลูกค้าหลายรายพร้อมกันพร้อมกัน ไคลเอนต์แต่ละตัวจะได้รับการจัดการโดยเธรดแยกต่างหากจาก Threadpool ขนาดพูลเริ่มต้นคือ 4 และสามารถเปลี่ยนแปลงได้ในฟังก์ชั่น Main
เซิร์ฟเวอร์จะรองรับคลาสใด ๆ ที่สืบทอดมาจาก httpservlet คลาสดังกล่าวสามารถเพิ่มได้โดยใช้ ServletContainer::addRoute
ฟังก์ชั่นที่สำคัญที่สุดของคลาสเหล่านี้ถูกนำมาใช้ คุณสามารถเขียนถึงไคลเอนต์โดยใช้ (เท่านั้น) PrintWriter ตั้งค่าส่วนหัวและสถานะการตอบกลับ HttpservletRequest สามารถแยก URL คำขอ, วิธี HTTP (รับ, โพสต์, ลบ, แพตช์), พารามิเตอร์การสืบค้นและพารามิเตอร์จากร่างกาย (ในกรณีของโพสต์) ข้อมูลจะถูกส่งไปยังไคลเอนต์หลังจากล้างบัฟเฟอร์หรือปิด HttpServletResponse การใช้ RequestDispatcher คุณสามารถส่งแบบสอบถามไปยัง Servlets อื่น ๆ รวมถึง JSP Servlets
มีการสนับสนุน async servlets หลังจากดำเนิน HttpServletRequest::startAsync แล้วคำขอจะเข้าสู่โหมดอะซิงโครนัส มีสองวิธีในการใช้โหมดนี้ รหัสใด ๆ จะทำงานตราบเท่าที่ AsyncContext::complete จะถูกดำเนินการในบางจุดซึ่งสิ้นสุดการเชื่อมต่อกับไคลเอนต์ นอกจากนี้คุณยังสามารถใช้ AsyncConext::start(Runnable) ที่นี่คุณต้องดำเนินการ AsyncContext::complete ในบางจุด หลังเข้ากันได้กับ Java Servlet API นอกจากนี้ยังรองรับการหมดเวลา ( AsyncContext::setTimeout ) และ AsyncListener Async Servlets ถูกนำมาใช้โดยใช้ CompletableFuture เพื่อให้พวกเขาจะใช้ Threadpool แยกต่างหากจากที่ใช้สำหรับไคลเอนต์ซิงโครนัส
หากแอปพลิเคชันได้รับการซิปใน. war และย้ายไปยังโฟลเดอร์ deploy มันจะถูกโหลดโดยอัตโนมัติ คลาสทั้งหมดที่มีคำอธิบายประกอบด้วย @WebServlet และสืบทอดจาก HttpServlet จะถูกเพิ่มลงในคอนเทนเนอร์ Servlet และจะมีให้ที่ localhost:port/warName/servletUrl แต่ละคลาสดังกล่าวจะต้องมีหนึ่ง servletUrl ที่ระบุไว้ในคำอธิบายประกอบ @WebServlet ในแอตทริบิวต์ค่า หากแอปพลิเคชันใช้ JSP มันจะถูกรวบรวมลงใน. class และโหลดโดยอัตโนมัติ อย่างไรก็ตามแอปพลิเคชันจำนวนมากสามารถโหลดได้ฉันไม่ทราบว่าจะเกิดอะไรขึ้นเมื่อมีชื่อคลาสเดียวกันกับที่ใช้ในสองแอปพลิเคชัน
เซิร์ฟเวอร์สามารถ transpile .jsp ไฟล์เป็น. class มีการรองรับไวยากรณ์เกือบทั้งหมดใน JSP ซึ่งรวมถึง:
<%@ page import/include=... %><% ... %><%! ... %><%= ... %><%-- %>${...} ${} ไวยากรณ์บางส่วนรองรับภาษานิพจน์ การดำเนินการทางคณิตศาสตร์อย่างง่ายสามารถทำได้และการแสดงออกของ instance.property1.property2 ซ์ฟอร์มใด ๆ property1.property2 จะถูกแปลงเป็น request.getAttribute("instance").getProperty1().getProperty2() ใน <% ... %> นอกจากนี้ยังมี out.println(...) ที่เขียนโดยตรงไปยังลูกค้า JSP สามารถแสดงได้โดยใช้ RequestDispatcher::forward หรือสามารถใช้ได้โดยตรงที่ localhost:8000/warName/jspFileName.jsp การกระทำ JSP ตัวอย่างมีให้ที่ localhost:8000/library/jsp โปรดทราบว่าฉันใช้การแยกวิเคราะห์ตัวเองดังนั้นการจัดรูปแบบรหัสแปลก/ไวยากรณ์อาจทำลายมัน
เพื่อแสดงให้เห็นถึงคอนเทนเนอร์ Servlet ฉันใช้แอปพลิเคชันง่าย ๆ ที่จำลองฐานข้อมูลหนังสือ สามารถเพิ่มหนังสือลบและอัปเดตโดยใช้แบบฟอร์ม HTML หนังสือทั้งหมดสามารถดูได้ในตาราง HTML ส่วนหน้าได้รับการพัฒนาอย่างเต็มที่โดยใช้ JSP แอปพลิเคชันถูกซิปใน Library.war โดยใช้งาน war Gradle ย้ายไปที่ deploy และโหลดไปยังเซิร์ฟเวอร์เมื่อเริ่มต้น
จุดสิ้นสุด:
มีการทดสอบมากกว่า 30 ครั้งเพื่อตรวจสอบฟังก์ชั่นส่วนใหญ่และแอปพลิเคชันตัวอย่าง เป็นที่ต้องการเรียกใช้พวกเขาจาก Intellij เนื่องจากปัญหาที่อธิบายไว้ใน คำถามที่พบบ่อย อีกทางเลือกหนึ่งสามารถเรียกใช้ได้โดยใช้:
./gradlew test
โครงการนี้เป็นส่วนหนึ่งของหลักสูตรมหาวิทยาลัยและฉันไม่ได้รับอนุญาตให้ใช้ห้องสมุดบุคคลที่สามใด ๆ ยกเว้น Junit
มีปัญหาเกี่ยวกับพอร์ตซ็อกเก็ตที่ใช้งานอยู่แล้ว ฉันไม่เคยลงไปแก้ไข แต่ส่วนใหญ่ใช้งานได้เมื่อวิ่งจาก Intellij
ตอนนี้ความละเอียดของ servlet ควรจัดการ URL ที่กำหนดนั้นค่อนข้างดั้งเดิม โดยทั่วไปแล้วเซิร์ฟเวอร์ url จะมองหา servlet ที่มี servletUrl เป็นคำนำหน้าของ url หากมีการเลือกจำนวนมากที่มีคำนำหน้ายาวที่สุด ซึ่งหมายความว่าหาก servlet มี servletUrl เท่ากับ /app/home มันจะจัดการ /app/home/nonexisting/ , /app/home/12345 เป็นต้น
ทุกคนยินดีที่จะมีส่วนร่วมในโครงการนี้ ฉันสร้างปัญหาที่เกิดขึ้นครั้งแรกก่อนดังนั้นอย่าลังเลที่จะตรวจสอบพวกเขา :)