เจาะโครงสร้าง IIS
กลไกการทำงานภายในของ IIS หน่วยการจัดการ การติดตั้ง module การกำหนดกฎการเขียน URL และแนวทางวิเคราะห์ปัญหา สำหรับการนำเว็บแอปพลิเคชันขึ้นให้บริการบน Windows Server
เอกสารนี้ทำไว้เพื่ออะไร
ระบบหนึ่งระบบประกอบด้วยซอฟต์แวร์หลายตัวที่ทำงานคนละหน้าที่ คนที่เพิ่งเข้ามาร่วมทีมมักเห็นแต่ละตัวแยกกัน แต่ยังไม่เห็นว่ามันต่อกันตรงไหน เอกสารนี้จึงเขียนขึ้นเพื่อตอบสามคำถาม
| คำถาม | ตอบอยู่ที่ |
|---|---|
| แต่ละตัวคืออะไร — IIS, Tomcat, Spring Boot, React คือซอฟต์แวร์ประเภทไหน ทำหน้าที่อะไร | 01 · 11 · 10 · 12 |
| แต่ละตัวทำงานอย่างไร — ข้างในมีองค์ประกอบอะไร รับ request มาแล้วทำอะไรต่อ ตั้งค่าที่ไหน | 02–09 |
| รวมกันแล้วเป็นระบบอย่างไร — ต่อกันตรงไหน ค่าใดต้องตรงกัน เรียกสถาปัตยกรรมแบบนี้ว่าอะไร | 13 |
สัดส่วนเนื้อหาเอนไปทาง IIS มากที่สุด เพราะเป็นตัวที่อยู่หน้าสุดและเป็นจุดที่ ปัญหาส่วนใหญ่เกิดขึ้น ส่วนที่ 01 ถึง 09 จึงเรียกปลายทางว่า upstream แบบกลาง ๆ ไว้ก่อน แล้วส่วนที่ 10 ถึง 12 ค่อยลงรายละเอียดว่าปลายทางจริงคือ Spring Boot บน Tomcat 11 และ frontend คือ React
ไม่ใช่เพราะบังคับ แต่เพราะ ระบบรันอยู่บน Windows Server ซึ่งมี IIS เป็น web server ติดมากับระบบปฏิบัติการอยู่แล้ว เมื่อสถาปัตยกรรมต้องการ reverse proxy อยู่แล้ว การใช้ของที่มีอยู่จึงสมเหตุสมผลกว่า การติดตั้งตัวกลางเพิ่ม
สิ่งที่อยู่นอกขอบเขต ได้แก่ การเขียนโค้ดแอปพลิเคชัน การออกแบบฐานข้อมูล และ business logic ของระบบ
ชื่อ site path ของไฟล์ และหมายเลข port ที่ปรากฏในคำสั่งทั้งหมด เป็น ค่าตัวอย่างที่กำหนดขึ้นเพื่อให้คำสั่งเป็นรูปธรรม เมื่อนำไปใช้งานจริงให้แทนที่ด้วยค่าที่องค์กรกำหนด
ค่าอ้างอิงที่ใช้ตลอดเอกสาร
| รายการ | ค่าที่ใช้ |
|---|---|
| โดเมนที่ผู้ใช้เรียก | https://app.corp.local |
| IIS Site | corp-web |
| Application Pool | CorpWebPool |
| โฟลเดอร์ไฟล์ static | D:\apps\corp-web |
| upstream ของ proxy | http://127.0.0.1:8080 |
| path บน upstream | /corp-api |
โครงสร้างเนื้อหา
| ส่วน | หัวข้อ | สาระสำคัญ |
|---|---|---|
| 01–03 | พื้นฐานและสถาปัตยกรรม | องค์ประกอบภายในของ IIS หน่วยการจัดการ และหน้าที่สองประการที่ IIS รับผิดชอบ |
| 04–06 | การติดตั้งและกำหนดค่า | ติดตั้ง module สร้าง Application Pool และ Site และกำหนดกฎใน web.config |
| 07–09 | การใช้งานจริง | การส่งต่อข้อมูล client การกำหนด HTTPS และแนวทางวิเคราะห์ปัญหา |
| 10–12 | ฝั่งแอปพลิเคชัน | การเตรียม Spring Boot และ React ให้ค่าตรงกับ IIS · Tomcat และการพัฒนาแบบแยกส่วน |
| 13 | Full Stack Architecture | ร้อยทุกส่วนเข้าด้วยกัน ชื่อเรียกของสถาปัตยกรรม เหตุผลที่เลือก และค่าที่ต้องตรงกัน |
องค์ประกอบภายในของ IIS
ความเข้าใจในส่วนนี้เป็นพื้นฐานของการวิเคราะห์ปัญหาในส่วนที่ 09
IIS มักถูกเข้าใจว่าเป็นโปรแกรมเดียว แต่ในทางเทคนิคประกอบด้วยองค์ประกอบสามส่วนที่อยู่คนละระดับของระบบปฏิบัติการ ส่วนหนึ่งทำงานในเคอร์เนลของ Windows ส่วนหนึ่งทำงานเป็น Windows Service และอีกส่วนเป็น process ที่ถูกสร้างและยุติตามปริมาณการใช้งาน
LocalSystem ใน svchost.exe เดียวกัน
และ w3wp.exe คือ process ที่ประมวลผล request จริง หนึ่ง process ต่อหนึ่ง Application Pool
- HTTP.sysKERNEL-MODE DRIVER
- องค์ประกอบที่ รับฟัง port 80 และ 443 โดยตรง เอกสารของ Microsoft ระบุคุณประโยชน์ไว้สามประการ ได้แก่ การ cache ระดับเคอร์เนล ซึ่งตอบ request ที่ cache ไว้โดยไม่ต้องสลับไปยัง user mode, การจัดคิวระดับเคอร์เนล ซึ่งเก็บ request ไว้จนกว่าจะมี worker process พร้อมรับ และการกรองความปลอดภัยเบื้องต้น
- W3SVCWWW PUBLISHING SERVICE
- ตั้งแต่ IIS 7 เป็นต้นมา W3SVC ไม่ได้ทำหน้าที่จัดการ worker process อีกต่อไป บทบาทที่คงเหลือคือเป็น listener adapter ของ HTTP.sys ได้แก่ การกำหนดค่าให้ HTTP.sys การปรับปรุงค่าเมื่อ configuration เปลี่ยนแปลง และการแจ้ง WAS เมื่อมี request เข้าสู่คิว
- WASWINDOWS PROCESS ACTIVATION SERVICE
-
องค์ประกอบที่ อ่าน
applicationHost.configและบริหารจัดการ Application Pool พร้อมทั้ง worker process เมื่อ pool ใดยังไม่มี worker process WAS จะเป็นผู้เริ่มการทำงานให้ และเมื่อไฟล์ configuration เปลี่ยนแปลง WAS จะได้รับการแจ้งเตือนและปรับปรุงค่าตาม - w3wp.exeWORKER PROCESS
- process ที่ประมวลผล request จริง โดยค่าเริ่มต้นมีหนึ่ง process ต่อหนึ่ง Application Pool ภายในประกอบด้วยลำดับของ module ที่ทำงานต่อเนื่องกัน ได้แก่ module static file, module rewrite และ module proxy เป็นต้น
ลำดับการประมวลผล request
| ขั้น | องค์ประกอบ | การทำงาน |
|---|---|---|
| 1 | HTTP.sys | ดักจับ request ที่ส่งมาจากเบราว์เซอร์ |
| 2 | HTTP.sys → WAS | ติดต่อ WAS เพื่อขอข้อมูลจาก configuration store |
| 3 | WAS | ร้องขอข้อมูลการตั้งค่าจาก applicationHost.config |
| 4 | W3SVC | รับข้อมูลการตั้งค่าของ application pool และ site |
| 5 | W3SVC → HTTP.sys | นำข้อมูลการตั้งค่าไปกำหนดให้ HTTP.sys |
| 6 | WAS | เริ่มการทำงานของ worker process สำหรับ pool ที่เกี่ยวข้อง |
| 7 | w3wp.exe | ประมวลผลและส่ง response กลับไปยัง HTTP.sys |
| 8 | HTTP.sys | ส่ง response ถึง client |
ขั้นที่ 2 ถึง 5 เกิดขึ้นเฉพาะกรณีที่ยังไม่มีข้อมูลใน cache หรือ configuration เพิ่งมีการเปลี่ยนแปลง request ในลำดับถัดไปจะเข้าสู่คิวของ worker process ที่ทำงานอยู่แล้วโดยตรง
request บางส่วนถูกปฏิเสธตั้งแต่ชั้น HTTP.sys ซึ่งยังไม่ถึง w3wp.exe
จึง ไม่ปรากฏใน log ของ IIS แต่ถูกบันทึกในไฟล์ httperr แทน
ข้อเท็จจริงนี้อธิบายอาการที่ผู้ใช้แจ้งว่าเข้าใช้งานไม่ได้ แต่ไม่พบข้อมูลใดใน log
รายละเอียดอยู่ในส่วนที่ 09
หน่วยการจัดการทั้งสี่
Site, Application และ Virtual Directory เป็นแกนของ URL ส่วน Application Pool เป็นแกนของ process ทั้งสองแกนเป็นอิสระต่อกัน
ความเข้าใจที่คลาดเคลื่อนที่พบบ่อยที่สุดคือการมองว่า Application Pool อยู่ภายใต้ Site ในความเป็นจริง Site กำหนดว่า URL ใดแมปไปยังไฟล์ใด ขณะที่ Pool กำหนดว่าโค้ดนั้นทำงานใน process ใด และ Application จาก Site ที่ต่างกันสามารถใช้ Pool ร่วมกันได้
- Site
- หน่วยระดับบนสุด มี binding เป็นของตนเอง และมี physical path เป็นรากของไฟล์
- corp-web → D:\apps\corp-web → *:443:app.corp.local
- Application
- ขอบเขตเชิงตรรกะภายใน site ข้อแตกต่างจากโฟลเดอร์ทั่วไปคือ สามารถกำหนด Application Pool ของตนเองได้
- /reports → D:\apps\corp-reports → pool "ReportsPool"
- Virtual Directory
- การแมป URL ไปยังโฟลเดอร์จริงที่อยู่คนละตำแหน่งหรือคนละไดรฟ์ ต้องอยู่ภายใน Application เสมอ และใช้ pool ของ Application ที่ครอบอยู่
- /reports/files → E:\archive
- Application Pool
- ขอบเขตเชิง process เทียบเท่ากับ
w3wp.exeหนึ่ง process แอปพลิเคชันที่อยู่คนละ pool แยกหน่วยความจำและสิทธิ์ออกจากกันโดยสมบูรณ์ - เอกสารของ Microsoft ระบุว่า pool ถูกออกแบบมาเพื่อ ป้องกันไม่ให้แอปพลิเคชันหนึ่งส่งผลกระทบต่อแอปพลิเคชันอื่นบนเครื่องเดียวกัน
Binding — องค์ประกอบสามส่วนที่ต้องไม่ซ้ำกัน
Binding กำหนดว่า request ลักษณะใดจะเข้าสู่ site นี้ ประกอบด้วย IP : port : ชื่อโฮสต์ ชุดข้อมูลสามส่วนนี้ต้องไม่ซ้ำกับ site อื่นบนเครื่องเดียวกัน ซึ่งเป็นเหตุผลที่ระบบหลายระบบ สามารถใช้ port 443 ร่วมกันได้ตราบใดที่ชื่อโฮสต์แตกต่างกัน
การกำหนด binding ของ IIS ให้ตรงกับ port ที่ upstream ใช้งาน เช่นกำหนด site ไว้ที่ 8080 ขณะที่ upstream ก็รับฟังที่ 8080 เช่นกัน จะทำให้ port ขัดแย้งกัน และมีบริการหนึ่งไม่สามารถเริ่มทำงานได้ binding ของ IIS ควรเป็น port ที่ผู้ใช้เรียกเท่านั้น ส่วน port ของ upstream ให้ระบุในกฎการส่งต่อ ตามส่วนที่ 06
ลำดับชั้นของไฟล์ configuration
ค่าที่อยู่ในลำดับล่างมีผลเหนือค่าที่อยู่ในลำดับบน ประเด็นสำคัญคือ web.config
อยู่ภายใน physical path ของแอปพลิเคชัน จึง ถูกนำติดไปพร้อมกับไฟล์
เมื่อคัดลอกโฟลเดอร์ไปยังเครื่องใหม่ กฎ rewrite จะถูกนำไปด้วยโดยอัตโนมัติ
machine.config ระดับ .NET ทั้งเครื่อง applicationHost.config ไฟล์หลักของ IIS — WAS อ่านไฟล์นี้ %windir%\system32\inetsrv\config\ D:\apps\corp-web\web.config ระดับ site — ตำแหน่งของกฎ rewrite D:\apps\corp-web\sub\web.config ระดับโฟลเดอร์ย่อย
หน้าที่สองประการของ IIS
ส่งไฟล์จากดิสก์ และส่งต่อ request ไปยัง upstream
เมื่อ request เข้าสู่ w3wp.exe ตามลำดับในส่วนที่ 01 แล้ว
IIS จะตัดสินใจอย่างใดอย่างหนึ่งในสองทางนี้เสมอ ทางแรกคือ
อ่านไฟล์จากดิสก์แล้วส่งกลับไปโดยตรง ซึ่งใช้กับไฟล์ HTML, JavaScript, CSS
และไฟล์ภาพ ทางที่สองคือ ส่ง request ต่อไปยังเซิร์ฟเวอร์อื่นแล้วนำผลลัพธ์กลับมาให้ผู้ใช้
ซึ่งคือการทำ reverse proxy
ตัวที่กำหนดว่าจะเลือกทางใด คือกฎการเขียน URL ที่จะกล่าวถึงในส่วนที่ 06 ผลลัพธ์ที่ได้คือ ทุกองค์ประกอบอยู่ภายใต้โดเมนเดียวกัน ในสายตาของเบราว์เซอร์ จึงไม่จำเป็นต้องกำหนดค่า CORS และ cookie ของการเข้าสู่ระบบทำงานได้ตามปกติ
127.0.0.1
เหตุผลของการผูก upstream ไว้ที่ loopback
การกำหนดให้ upstream รับฟังเฉพาะ 127.0.0.1 แทน 0.0.0.0
ทำให้ ไม่มีผู้ใดเรียกเข้าไปได้โดยตรงจากเครือข่าย ทุก request
ต้องผ่าน IIS ซึ่งเป็นจุดเดียวที่บังคับใช้ TLS ตรวจสอบสิทธิ์ระดับเครือข่าย และบันทึก log
ข้อกำหนดนี้เป็นเงื่อนไขจำเป็นของแนวทางในส่วนที่ 07 ด้วย
เว็บแอปพลิเคชันที่จัดการ client-side routing หรือที่เรียกว่า SPA
ไม่ได้มีไฟล์ /orders/1024.html อยู่บนดิสก์จริง
หากผู้ใช้สั่ง refresh ที่หน้าดังกล่าว IIS จะไม่พบไฟล์และตอบกลับด้วยสถานะ 404
กฎข้อที่สองจึงกำหนดว่า หาก path ที่ร้องขอไม่ใช่ไฟล์และไม่ใช่โฟลเดอร์จริง
ให้ส่ง index.html กลับไปแทน เพื่อให้โค้ดฝั่ง client ดำเนินการต่อ
การติดตั้งองค์ประกอบที่จำเป็น
IIS มาตรฐานไม่รองรับการทำ reverse proxy ต้องติดตั้ง module เพิ่มสองรายการ และลำดับการติดตั้งมีผลต่อการทำงาน
คำสั่งทั้งหมดในส่วนนี้ต้องดำเนินการผ่าน PowerShell ที่เปิดด้วยสิทธิ์ Run as administrator
เนื่องจากเอกสารของ Install-WindowsFeature ระบุว่า cmdlet นี้ต้องการสิทธิ์ระดับยกระดับ
หากเซิร์ฟเวอร์เป็นเครื่องเสมือน ควรจัดทำ snapshot ไว้ก่อนเพื่อให้สามารถย้อนกลับได้
ติดตั้ง Web Server (IIS)
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
เอกสารของ cmdlet ระบุว่า เครื่องมือจัดการจะไม่ถูกติดตั้งโดยค่าเริ่มต้น
เมื่อติดตั้งผ่าน Install-WindowsFeature ต้องเพิ่มพารามิเตอร์นี้จึงจะได้มาด้วย
ซึ่งต่างจากการติดตั้งผ่าน Add Roles and Features Wizard ที่ติดตั้งให้อยู่แล้ว
หากต้องการให้เครื่องเริ่มระบบใหม่อัตโนมัติเมื่อการติดตั้งกำหนดให้ต้องทำ
สามารถเพิ่มพารามิเตอร์ -Restart ตามที่เอกสารระบุไว้
Get-WindowsFeature -Name Web-Server
ซึ่งเป็น cmdlet ที่เอกสารระบุไว้ในรายการที่เกี่ยวข้อง
ตรวจสอบสถานะของบริการ
Get-Service W3SVC, WAS | Select-Object Name, Status Invoke-WebRequest http://localhost -UseBasicParsing | Select-Object StatusCode
W3SVC และ WAS มีสถานะ Running
ซึ่งเป็นองค์ประกอบสองส่วนตามรูปที่ 1 และคำสั่งที่สองคืนค่า 200
ติดตั้ง URL Rewrite
module นี้ไม่ได้ติดตั้งมาพร้อม IIS ต้องดาวน์โหลด standalone installer จากเว็บไซต์ IIS ของ Microsoft
เวอร์ชันปัจจุบันคือ URL Rewrite 2.1 ซึ่งเป็นองค์ประกอบที่รองรับการกำหนดกฎ
<rewrite> ในไฟล์ web.config
ติดตั้ง Application Request Routing
Application Request Routing (ARR) เป็นองค์ประกอบที่ทำให้ IIS สามารถส่ง request ต่อไปยังเซิร์ฟเวอร์อื่นได้ เวอร์ชันปัจจุบันคือ ARR 3
เอกสารของ Microsoft ระบุไว้ชัดเจนว่า ARR มีความจำเป็นต้องพึ่งพา URL Rewrite จึงต้องติดตั้ง URL Rewrite ก่อนการติดตั้ง ARR เนื่องจาก ARR อาศัย URL Rewrite ในการตรวจสอบ request ขาเข้าเพื่อตัดสินใจเส้นทางการส่งต่อ หากติดตั้งสลับลำดับ ให้ดำเนินการติดตั้ง ARR ซ้ำอีกครั้งหลังจากติดตั้ง URL Rewrite เรียบร้อยแล้ว
เปิดใช้งาน Proxy
ขั้นตอนนี้เป็นจุดที่ถูกมองข้ามบ่อยที่สุด เอกสารของ Microsoft ระบุว่า ความสามารถด้าน reverse proxy ถูกปิดไว้เป็นค่าเริ่มต้น จึงต้องเปิดใช้งานก่อน ดำเนินการผ่าน IIS Manager โดยเลือก node ของเซิร์ฟเวอร์ เปิดรายการ Application Request Routing แล้วเลือก Enable Proxy โดยคงค่าอื่นไว้ตามค่าเริ่มต้น
กฎ rewrite ถูกกำหนดไว้อย่างถูกต้องทั้งหมด แต่ /api คืนค่าสถานะ
404 หรือ 500 เนื่องจาก IIS ตีความ URL ปลายทาง
เป็น path ภายในเครื่องแทนการส่งต่อออกไปภายนอก
ใช้ appcmd เพื่อแสดงรายการ module ที่ลงทะเบียนกับ IIS
ซึ่งเป็นวิธีที่ตรงที่สุดในการยืนยันว่าการติดตั้งสมบูรณ์
%windir%\system32\inetsrv\appcmd.exe list modules
การสร้าง Application Pool และ Site
มีค่าหนึ่งที่ต้องปรับจากค่าเริ่มต้นเสมอ
เตรียมโฟลเดอร์และวางไฟล์
New-Item -ItemType Directory "D:\apps\corp-web" -Force # คัดลอกไฟล์ static ทั้งชุดมาวางที่ตำแหน่งนี้ # ต้องมี index.html อยู่ที่ราก คือ D:\apps\corp-web\index.html
สร้าง Application Pool
Import-Module WebAdministration New-WebAppPool -Name "CorpWebPool" # กำหนดเป็น No Managed Code — ค่าว่างหมายถึงไม่โหลด .NET runtime Set-ItemProperty IIS:\AppPools\CorpWebPool -Name managedRuntimeVersion -Value ""
ค่าเริ่มต้นของ pool ที่สร้างใหม่คือการโหลด .NET CLR เข้าสู่ process
แต่ตามส่วนที่ 03 w3wp.exe ในสถาปัตยกรรมนี้ทำหน้าที่เพียงสองประการ
คือส่งไฟล์ static และส่ง request ต่อไปยัง upstream
โดยไม่มีโค้ด managed ที่ต้องประมวลผลภายใน process เลย
การกำหนดเป็นค่าว่างจึงเป็นการตัดองค์ประกอบที่ไม่ได้ใช้งานออก
ในหน้าจอ IIS Manager ค่านี้คือรายการ .NET CLR version → No Managed Code
สร้าง Site และผูกกับ Application Pool
New-Website -Name "corp-web" ` -PhysicalPath "D:\apps\corp-web" ` -ApplicationPool "CorpWebPool" ` -HostHeader "app.corp.local" -Port 80
ในขั้นนี้กำหนดที่ port 80 ก่อนเพื่อการทดสอบ แล้วจึงเพิ่ม binding ของ port 443 ในส่วนที่ 08
Get-Website แสดงรายการ corp-web ในสถานะ Started ·
เมื่อเรียก http://app.corp.local/ จากเบราว์เซอร์บนเซิร์ฟเวอร์ จะปรากฏหน้าแรก
โดยยังไม่สามารถเรียกหน้าย่อยได้ จนกว่าจะกำหนดกฎในส่วนที่ 06
สิทธิ์การเข้าถึงโฟลเดอร์
โดยค่าเริ่มต้น worker process ของแต่ละ pool ทำงานภายใต้บัญชีเสมือนชื่อ
IIS AppPool\<ชื่อ pool> ซึ่งถูกสร้างขึ้นอัตโนมัติพร้อมกับ pool
บัญชีนี้ต้องมีสิทธิ์อ่านโฟลเดอร์ที่เก็บไฟล์ static
icacls "D:\apps\corp-web" /grant "IIS AppPool\CorpWebPool":(OI)(CI)RX
หากโฟลเดอร์อยู่ภายใต้ path ที่สืบทอดสิทธิ์มาอย่างครบถ้วนอยู่แล้ว ขั้นตอนนี้อาจไม่จำเป็น
ให้ตรวจสอบด้วยการเรียกหน้าเว็บก่อน หากได้รับสถานะ 401 หรือ 403
จึงค่อยกำหนดสิทธิ์เพิ่ม
การกำหนดกฎใน web.config
ลำดับของกฎมีผลต่อการทำงานโดยตรง การสลับลำดับทำให้ระบบทำงานผิดพลาด
จัดวางไฟล์นี้ไว้ที่ D:\apps\corp-web\web.config
ซึ่งเป็นรากของ site ตำแหน่งเดียวกับ index.html
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <!-- กฎที่ 1 : ส่ง /api ต่อไปยัง upstream --> <rule name="api-to-backend" stopProcessing="true"> <match url="^api/(.*)" /> <action type="Rewrite" url="http://127.0.0.1:8080/corp-api/{R:1}" /> </rule> <!-- กฎที่ 2 : path ที่ไม่ใช่ไฟล์จริง ให้ส่ง index.html --> <rule name="spa-fallback" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
/api/... ไปก่อน
เพราะ path นั้นก็ไม่ใช่ไฟล์จริงเช่นกัน ผลคือได้ index.html กลับไปแทนข้อมูล
ซึ่งเป็นอาการที่หาสาเหตุได้ยากเพราะสถานะที่คืนกลับมายังเป็น 200
คำอธิบายรายองค์ประกอบ
| องค์ประกอบ | ความหมาย |
|---|---|
| ^api/(.*) | จับคู่ path ที่ขึ้นต้นด้วย api/ โดยวงเล็บทำหน้าที่สร้าง capture group เพื่อเก็บส่วนที่เหลือไว้ |
| {R:1} | back-reference ไปยัง capture group ลำดับแรก ซึ่งคือส่วนที่อยู่หลัง api/ |
| type="Rewrite" | เขียน URL ใหม่และส่งต่อภายในเซิร์ฟเวอร์ ซึ่งต่างจาก Redirect ที่จะสั่งให้เบราว์เซอร์เรียกใหม่เอง และทำให้ port ภายในเปิดเผยสู่ภายนอก |
| stopProcessing="true" | เมื่อกฎนี้จับคู่สำเร็จ ให้ยุติการประมวลผล ไม่ต้องพิจารณากฎลำดับถัดไป |
| IsFile / IsDirectory + negate="true" |
เงื่อนไขที่ระบุว่า path นี้ไม่ใช่ไฟล์จริงและไม่ใช่โฟลเดอร์จริง ซึ่งเป็นวิธีแยก client-side routing ออกจากไฟล์ .js และ .css ที่มีอยู่บนดิสก์ |
หากจัดวางกฎ spa-fallback ไว้ก่อน กฎดังกล่าวจะจับคู่กับ /api/orders/1024 ด้วย
เนื่องจาก path นั้นไม่ใช่ไฟล์จริงเช่นเดียวกัน ส่งผลให้ระบบคืนค่า index.html
แทนข้อมูล JSON อาการที่ปรากฏคือ การเรียก request สำเร็จด้วยสถานะ 200
แต่ข้อมูลที่ได้รับเป็น HTML ซึ่งเป็นปัญหาที่ตรวจหาสาเหตุได้ยาก
หลักการคือกฎที่มีเงื่อนไขเฉพาะเจาะจงกว่าต้องอยู่ก่อนกฎที่มีเงื่อนไขกว้างกว่าเสมอ
จะสังเกตได้ว่าการเรียกจากภายนอกใช้ /api/orders/1024
ขณะที่ upstream รับเป็น /corp-api/orders/1024
โดยมีกฎ rewrite ทำหน้าที่แปลง ซึ่งเป็นข้อดีในเชิงปฏิบัติ คือ
สามารถเปลี่ยน path ของ upstream ได้โดยไม่ต้องแก้ไขโค้ดฝั่ง frontend
เพียงแก้ไขค่าเดียวในไฟล์ web.config
ชนิดไฟล์ที่ IIS ไม่รู้จัก
IIS ตรวจสอบนามสกุลไฟล์กับรายการ MIME type ที่กำหนดไว้ก่อนส่งออกเสมอ
เอกสารของ <mimeMap> ระบุพฤติกรรมนี้ไว้ว่า
IIS จะไม่ส่งคืนชนิดไฟล์ที่ไม่ได้เพิ่มไว้ในองค์ประกอบ <staticContent>
หรือไม่มี mapping ใน <handlers>
และอธิบายว่าเป็นพฤติกรรมที่ออกแบบมาเพื่อป้องกันการเข้าถึงไฟล์ที่ไม่ได้ประกาศไว้ใน configuration
status code ที่ได้รับในกรณีนี้คือ 404.3
ซึ่งเอกสารรายการ status code ของ IIS นิยามไว้ว่า MIME type restriction
หมายถึง MIME mapping ปัจจุบันสำหรับนามสกุลที่ร้องขอไม่ถูกต้องหรือยังไม่ได้กำหนดค่า
วิธีแก้คือประกาศนามสกุลนั้นเพิ่มในไฟล์ web.config เดียวกัน
เอกสารกำหนดว่าแต่ละรายการต้องประกอบด้วยสองส่วน คือ
นามสกุลไฟล์ที่ไม่ซ้ำกันในแอตทริบิวต์ fileExtension และ
ชนิดของไฟล์ในแอตทริบิวต์ mimeType
ตัวอย่างด้านล่างคัดมาจากเอกสารทางการโดยตรง
<configuration> <system.webServer> <staticContent> <mimeMap fileExtension=".syx" mimeType="application/octet-stream" /> <mimeMap fileExtension=".tab" mimeType="text/plain" /> </staticContent> </system.webServer> </configuration>
ให้แทนนามสกุลและชนิดไฟล์ด้วยค่าที่ระบบต้องใช้จริง โดยตรวจสอบก่อนว่านามสกุลใดที่ IIS บนเซิร์ฟเวอร์นั้นยังไม่รู้จัก ซึ่งดูรายการที่มีอยู่ได้จาก IIS Manager ที่รายการ MIME Types ตามขั้นตอนที่เอกสารระบุไว้ แล้วจึงประกาศเฉพาะรายการที่ขาด
การส่งข้อมูล client ไปยัง upstream
เมื่อมี proxy คั่นกลาง upstream จะได้รับ IP ของ IIS แทน IP ของผู้ใช้
ประเด็นนี้เป็นผลสืบเนื่องที่เกิดขึ้นกับสถาปัตยกรรม reverse proxy ทุกรูปแบบ และส่งผลกระทบต่อสองเรื่องที่มักพบเมื่อระบบเริ่มใช้งานจริง ได้แก่ การบันทึก IP ที่คลาดเคลื่อนใน log ของ upstream และลิงก์ที่เซิร์ฟเวอร์นั้นสร้างขึ้นเองซึ่งชี้กลับไปยังชื่อโฮสต์ภายใน
หมายเลข IP ของผู้ใช้
เมื่อ IIS ส่ง request ต่อไปยัง upstream การเชื่อมต่อในช่วงนั้นเกิดจาก IIS เอง upstream จึงเห็น IP ต้นทางเป็นของ IIS ไม่ใช่ของผู้ใช้ ข้อมูล IP เดิมจะเดินทางต่อไปได้ก็ต่อเมื่อถูกใส่ไว้ใน header
header ที่ IIS ใช้กับข้อมูลนี้คือ X-Forwarded-For
ซึ่งยืนยันได้จากรายการ status code ของ IIS ที่กำหนดรหัสย่อย 400.10
ไว้ตั้งแต่ IIS 8.0 โดยมีนิยามว่า Invalid X-Forwarded-For (XFF) header
กล่าวคือ IIS ตรวจสอบความถูกต้องของ header นี้และปฏิเสธ request
เมื่อค่าไม่ถูกต้อง
ก่อนนำไปใช้จริง ให้ทดสอบด้วยการอ่าน header ที่ upstream ได้รับจริงหนึ่งครั้ง เพื่อยืนยันว่ามีค่านี้เดินทางมาถึงและอยู่ในรูปแบบที่แอปพลิเคชันอ่านได้
ชื่อโฮสต์เดิม
ค่า preserveHostHeader ทำให้ request ที่ส่งต่อไปยัง upstream
มี header Host เช่นเดียวกับที่ได้รับเข้ามาที่ IIS
แทนที่จะเป็นค่าที่ถูกแก้ไขเพื่อการ routing ของ ARR
เอกสารกรณีศึกษาการใช้ ARR ของ Microsoft ระบุเหตุผลของการตั้งค่านี้เป็น true
ไว้ตรงกันว่า แอปพลิเคชันจำเป็นต้องเห็น host header เดิมบน request ที่ถูก proxy มา
ซึ่งจำเป็นเมื่อ upstream สร้าง absolute URL จากค่า Host ที่ได้รับ
เอกสารฉบับเดียวกันแสดงให้เห็นว่าค่าในกลุ่มนี้อยู่ในองค์ประกอบ
<proxy> ของไฟล์ applicationHost.config
ซึ่งเป็นระดับเซิร์ฟเวอร์ ไม่ใช่ระดับ site
ตั้งผ่าน IIS Manager โดยเลือก node ของเซิร์ฟเวอร์ แล้วเปิดรายการ
Application Request Routing ซึ่งเป็นหน้าเดียวกับที่ใช้เปิด Enable Proxy
ในส่วนที่ 04 ค่าทั้งหมดในหน้านั้นจะถูกบันทึกลงองค์ประกอบ <proxy>
ของ applicationHost.config ให้เอง
header เป็นข้อมูลที่ client สามารถกำหนดค่าเองได้ หาก upstream จะเชื่อถือค่าจาก
X-Forwarded-For หรือ header ที่ระบุตัวตนผู้ใช้ซึ่ง proxy เป็นผู้กำหนด
ต้องปฏิบัติตามเงื่อนไขสองข้อพร้อมกัน
- proxy ต้องเขียนทับค่าใน header นั้นเสมอ ไม่ใช่การต่อท้าย มิฉะนั้นค่าที่ client กำหนดมาเองจะปะปนอยู่ในข้อมูล
- upstream ต้องเข้าถึงได้จาก proxy เท่านั้น
กล่าวคือต้องผูกไว้ที่
127.0.0.1ตามรูปที่ 3 หากผู้ใดสามารถเรียกตรงไปยัง port 8080 ได้ ผู้นั้นจะกำหนด header ใดก็ได้ และระบบจะเชื่อถือค่านั้นทันที
การส่งผลการยืนยันตัวตนของ Windows
กรณีที่องค์กรต้องการให้ IIS เป็นผู้ยืนยันตัวตนกับ Active Directory
แล้วส่งผลการตรวจสอบต่อไปให้ upstream จะต้องเปิดใช้งาน Windows Authentication
เอกสารของ <windowsAuthentication> ระบุว่า
การติดตั้ง IIS ตามค่าเริ่มต้นไม่มี role service นี้มาด้วย
และกำหนดลำดับการดำเนินการไว้สามขั้นตามนี้
- ติดตั้ง role service ชื่อ Windows Authentication ผ่าน Server Manager ที่ Web Server (IIS) → Web Server → Security → Windows Authentication
- ปิด Anonymous authentication ของ site หรือ application นั้น
- เปิด Windows authentication ของ site หรือ application นั้น
หลังติดตั้ง role service แล้ว IIS จะเขียนค่า <windowsAuthentication enabled="false" />
ลงใน ApplicationHost.config กล่าวคือ ค่าเริ่มต้นของแอตทริบิวต์ enabled คือ false
จึงต้องเปิดเองเสมอ
appcmd.exe set config "corp-web" ^ -section:system.webServer/security/authentication/anonymousAuthentication ^ /enabled:"False" /commit:apphost appcmd.exe set config "corp-web" ^ -section:system.webServer/security/authentication/windowsAuthentication ^ /enabled:"True" /commit:apphost
เอกสารกำกับไว้ว่า ต้องกำหนด /commit:apphost เมื่อใช้ AppCmd.exe
กับการตั้งค่าเหล่านี้ เพื่อให้ค่าถูกบันทึกลงใน location section
ที่ถูกต้องของ ApplicationHost.config
เมื่อเปิดค่านี้แล้ว IIS จะเป็นผู้จัดการการยืนยันตัวตนทั้งหมด และ upstream จะได้รับเฉพาะ request ที่ผ่านการตรวจสอบแล้ว ส่วนการส่งชื่อผู้ใช้ต่อไปให้เซิร์ฟเวอร์นั้น ต้องกำหนดเป็น header เพิ่มเติม ซึ่งอยู่ภายใต้ข้อกำหนดด้านความปลอดภัยข้างต้นทุกประการ
การกำหนด HTTPS และ Binding
TLS สิ้นสุดที่ชั้น IIS ส่วนการสื่อสารภายในเป็น HTTP ธรรมดาบน loopback
ในสถาปัตยกรรมนี้ IIS เป็นจุดเดียวที่รับ TLS ขณะที่ช่วงระหว่าง IIS กับ upstream
สื่อสารผ่าน 127.0.0.1 ซึ่งไม่ออกจากเครื่อง จึงไม่จำเป็นต้องทำ TLS ซ้ำอีกชั้น
# แสดงใบรับรองที่ติดตั้งอยู่ในเครื่อง และบันทึก Thumbprint ของใบที่จะใช้งาน Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Thumbprint, NotAfter # ผูก binding แบบ https เข้ากับ site New-WebBinding -Name "corp-web" -Protocol https -Port 443 ` -HostHeader "app.corp.local" -SslFlags 1
ค่า -SslFlags ระบุชนิดของใบรับรองและที่เก็บ เอกสารของ cmdlet กำหนดค่าไว้สี่ค่า คือ
0 ใบรับรองปกติใน Windows certificate storage ·
1 ใบรับรองแบบ SNI ·
2 central certificate store ·
3 SNI ใน central certificate store
ค่า 1 จึงเป็นการเปิดใช้งาน SNI ซึ่งทำให้ site หลายรายการ
ใช้ใบรับรองต่างกันบน port 443 เดียวกันได้ ตามหลักการ binding ในส่วนที่ 02
คำสั่ง New-WebBinding สร้างเฉพาะ binding เท่านั้น ยังไม่ได้ผูกใบรับรองเข้าไป
หากหยุดเพียงเท่านี้ site จะยังให้บริการ HTTPS ไม่ได้
ต้องนำ Thumbprint ที่ได้จากขั้นที่ 1 มาผูกกับ binding อีกขั้นหนึ่ง
ซึ่งเป็นลำดับที่ปรากฏในตัวอย่างของเอกสาร cmdlet โดยตรง
# "my" คือชื่อ store ซึ่งตรงกับ Cert:\LocalMachine\My (Get-WebBinding -Name "corp-web" -Port 443 -Protocol "https").AddSslCertificate( "a909502dd82ae41433e6f83886b00d4277a32a7b", "my")
แทนที่สตริง Thumbprint ด้วยค่าจริงของใบรับรองที่องค์กรใช้ ค่าที่แสดงไว้เป็นตัวอย่างจากเอกสารของ Microsoft
การบังคับใช้ HTTPS
เพิ่มกฎต่อไปนี้เป็นกฎลำดับแรกสุด ภายใน <rules>
ของไฟล์ web.config โดยอยู่ก่อนกฎทั้งสองข้อในส่วนที่ 06
<rule name="force-https" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="^OFF$" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule>
กรณีนี้ใช้ Redirect แทน Rewrite เนื่องจากมีเจตนาให้เบราว์เซอร์
เปลี่ยน URL บนแถบที่อยู่จริง ซึ่งตรงข้ามกับกรณีของ /api ในส่วนที่ 06
ที่ต้องการปกปิดโครงสร้างภายในจากผู้ใช้
การตรวจสอบและวิเคราะห์ปัญหา
ระบุ log ที่ถูกต้องก่อน แล้วจึงวิเคราะห์สาเหตุ
ตามรูปที่ 1 request เดินทางผ่านหลายชั้น และ แต่ละชั้นบันทึกข้อมูลลงคนละไฟล์ การเปิด log ที่ไม่ตรงกับชั้นที่เกิดปัญหา เป็นสาเหตุที่ทำให้เข้าใจว่าระบบไม่ได้บันทึกข้อมูลใดไว้
w3wp.exe
ให้ตรวจสอบไฟล์ httperr เป็นลำดับแรก
อาการที่พบบ่อยและจุดที่ควรตรวจสอบก่อน
| อาการ | จุดที่ควรตรวจสอบก่อน |
|---|---|
| หน้าแรกเปิดได้ แต่การเรียกหน้าย่อยคืนสถานะ 404 | ยังไม่ได้กำหนดกฎ spa-fallback หรือยังไม่ได้ติดตั้ง URL Rewrite (ส่วนที่ 04) |
/api คืนสถานะ 404 ขณะที่ upstream ทำงานปกติ |
ยังไม่ได้เปิดใช้งาน Enable Proxy ของ ARR ซึ่งเป็นจุดที่ถูกมองข้ามบ่อยที่สุด (ส่วนที่ 04) |
| ไฟล์บางชนิดคืนสถานะ 404.3 | นามสกุลไฟล์นั้นไม่ปรากฏใน MimeMap ให้ประกาศเพิ่มใน staticContent (ส่วนที่ 06) |
| request คืนสถานะ 200 แต่ข้อมูลที่ได้รับเป็น HTML | ลำดับของกฎสลับกัน ทำให้ spa-fallback จับคู่กับ /api ก่อน (ส่วนที่ 06) |
/api คืนสถานะ 502 |
upstream ไม่ได้ทำงานอยู่ หรือรับฟังคนละ port กับที่ระบุไว้ในกฎ rewrite |
| คืนสถานะ 401 หรือ 403 กับไฟล์ static | บัญชี IIS AppPool\CorpWebPool ยังไม่มีสิทธิ์อ่านโฟลเดอร์ (ส่วนที่ 05) |
| ผู้ใช้แจ้งว่าเข้าใช้งานไม่ได้ แต่ log ของ IIS ไม่มีข้อมูล | ไฟล์ httperr ตามรูปที่ 4 เนื่องจาก request ถูกปฏิเสธตั้งแต่ชั้น HTTP.sys |
log ปลายทางแสดง IP เป็น 127.0.0.1 ทุกรายการ |
upstream ยังไม่ได้อ่านค่าจาก header X-Forwarded-For (ส่วนที่ 07) |
เรียกตรงไปยัง upstream จากบนเซิร์ฟเวอร์เดียวกันด้วยคำสั่ง
curl http://127.0.0.1:8080/corp-api/...
หากได้รับผลลัพธ์ที่ถูกต้อง แสดงว่าปลายทางทำงานปกติ
ให้ย้อนกลับไปตรวจสอบกฎ rewrite และการเปิดใช้งาน Enable Proxy
แต่หากผลลัพธ์ผิดพลาดตั้งแต่ขั้นนี้ แสดงว่าสาเหตุไม่ได้อยู่ที่ IIS
การเปิด Failed Request Tracing
เมื่อ log ปกติยังไม่เพียงพอ IIS มีเครื่องมือที่บันทึกรายละเอียดของ request
ที่ล้มเหลวเป็นรายครั้ง เอกสารของ Microsoft อธิบายหลักการทำงานไว้ว่า
เครื่องมือนี้เก็บ trace event ของ request ไว้ในบัฟเฟอร์
และเขียนลงดิสก์เฉพาะเมื่อ request นั้นล้มเหลว
โดยผู้ดูแลเป็นผู้กำหนดเองว่าอะไรนับเป็นความล้มเหลว
จึงเป็นวิธีที่ตรงที่สุดในการหาสาเหตุของ 404.3 และปัญหาที่เกิดจากกฎ rewrite
ส่วนประกอบที่ต้องติดตั้งคือ Tracing ซึ่งอยู่ใต้ World Wide Web Services → Health and Diagnostics → Tracing ตามที่เอกสารระบุ จากนั้นเปิดใช้งานที่ระดับ site ผ่าน IIS Manager โดยเลือก site แล้วเลือก Failed Request Tracing ในบานหน้าต่าง Actions และเพิ่มกฎที่ Failed Request Tracing Rules พร้อมระบุ status code ที่ต้องการติดตาม
<location path="corp-web"> <system.webServer> <tracing> <traceFailedRequests> <add path="*"> <traceAreas> <add provider="WWW Server" areas="Security" verbosity="Verbose" /> </traceAreas> <failureDefinitions statusCodes="404.3" /> </add> </traceFailedRequests> </tracing> </system.webServer> </location>
ไฟล์ผลลัพธ์ถูกเก็บไว้ที่ %SystemDrive%\inetpub\logs\FailedReqLogFiles\W3SVC<หมายเลข site>
โดยเขียน หนึ่งไฟล์ต่อหนึ่ง request ที่ล้มเหลว เช่น fr000001.xml
พร้อมไฟล์ freb.xsl หนึ่งไฟล์ต่อหนึ่งโฟลเดอร์สำหรับใช้เปิดดูผลลัพธ์
การเตรียม Spring Boot ให้ทำงานกับ IIS
งานฝั่งแอปพลิเคชันที่ต้องสอดคล้องกับการตั้งค่า IIS ในส่วนก่อนหน้า
ส่วนที่ 01 ถึง 09 อธิบายเฉพาะชั้น IIS โดยเรียกปลายทางว่า upstream เพื่อให้ใช้ได้กับทุกเทคโนโลยี ส่วนนี้ลงรายละเอียดเฉพาะกรณีที่ปลายทางคือ Spring Boot ที่ทำงานบน Apache Tomcat โดยจำกัดเฉพาะจุดที่ต้องตั้งค่าให้สอดคล้องกับ IIS เท่านั้น มิได้ครอบคลุมการพัฒนาแอปพลิเคชัน
ขอบเขตที่ IIS ไม่เกี่ยวข้อง
ประเด็นที่ต้องเข้าใจก่อนคือ w3wp.exe ไม่ได้ประมวลผลโค้ด Java เลย
JVM ที่รัน Spring Boot อยู่ใน process ของ Tomcat ซึ่งแยกจากกันโดยสิ้นเชิง
IIS เพียงเปิดการเชื่อมต่อ HTTP ไปยัง port ที่ Tomcat รับฟัง แล้วส่งผลลัพธ์กลับมาให้ผู้ใช้
ตามที่อธิบายไว้ในส่วนที่ 03
- การรีสตาร์ท IIS ไม่กระทบสถานะที่เก็บอยู่ในหน่วยความจำของแอปพลิเคชัน Java และการ redeploy ไฟล์ WAR ก็ไม่กระทบไฟล์ static ที่ IIS ให้บริการอยู่
- การกำหนดหน่วยความจำเป็นคนละเรื่องกัน — ค่าของ Application Pool
ควบคุมเฉพาะ
w3wp.exeส่วนขนาด heap ของ JVM กำหนดที่ Tomcat - ด้วยเหตุผลเดียวกัน จึงไม่ต้องติดตั้ง .NET Hosting Bundle ซึ่งมีไว้ให้ IIS เรียกใช้โค้ด .NET ภายใน process ของตนเอง module ที่จำเป็นมีเพียง URL Rewrite และ ARR ตามส่วนที่ 04
ส่งมอบเป็น WAR ไม่ใช่ JAR
โดยค่าเริ่มต้น Spring Boot สร้างไฟล์ JAR ที่มี Tomcat ฝังอยู่ภายในและรันตัวเองได้ แต่เมื่อปลายทางคือ Tomcat ที่ติดตั้งแยกบนเครื่อง สิ่งที่ต้องส่งมอบคือไฟล์ WAR เอกสารของ Spring Boot กำหนดขั้นตอนการแปลงไว้ดังนี้
provided · ส่วนแถวล่างคือโซ่ของค่าที่ต้องตรงกันสามจุด
ตั้งแต่ชื่อไฟล์จนถึงกฎใน web.config
ให้คลาสหลักสืบทอด SpringBootServletInitializer
และ override เมธอด configure
@SpringBootApplication public class MyApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(MyApplication.class); } public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
เอกสารคงเมธอด main ไว้ในตัวอย่างด้วย
โดยระบุว่าเมื่อไฟล์ WAR ใช้งานได้แล้ว สามารถทำให้ไฟล์นั้นรันได้เองด้วยการเพิ่มเมธอด
main เข้าไปในคลาส
เปลี่ยน packaging เป็น war
<packaging>war</packaging>
ประกาศ servlet container ที่ฝังมาให้เป็น provided
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
ขั้นนี้ทำให้ Tomcat ที่ฝังมากับ Spring Boot ไม่ถูกบรรจุลงในไฟล์ WAR จึงไม่ไปซ้อนทับกับ Tomcat ที่ติดตั้งอยู่บนเครื่องจริง
แอปพลิเคชันที่ใช้ Spring WebFlux ไม่รองรับการ deploy แบบ WAR เอกสารให้เหตุผลว่า WebFlux ไม่ได้ผูกกับ servlet API โดยตรง และถูก deploy บนเซิร์ฟเวอร์ Reactor Netty ที่ฝังมาให้เป็นค่าเริ่มต้น หากระบบใช้ WebFlux จะต้องเลือกสถาปัตยกรรมอื่นแทนการวางบน Tomcat
context path มาจากชื่อไฟล์ WAR
ค่า /corp-api ที่ปรากฏในกฎ rewrite ของส่วนที่ 06
ไม่ได้ตั้งขึ้นลอย ๆ แต่ถูกกำหนดโดย ชื่อไฟล์ WAR ที่วางไว้ใน Tomcat
เอกสารของ Tomcat ระบุกฎการตั้งชื่อไว้ว่า หาก context path เป็นสตริงว่าง
ชื่อฐานของไฟล์จะเป็น ROOT ซึ่งเป็นตัวพิมพ์ใหญ่เสมอ
กรณีอื่นชื่อฐานคือ context path ที่ตัดเครื่องหมาย / นำหน้าออก
แล้วแทนที่ / ที่เหลือด้วย #
| context path | ชื่อไฟล์ WAR | URL ที่ Tomcat รับ |
|---|---|---|
| /foo | foo.war | /foo/... |
| /foo/bar | foo#bar.war | /foo/bar/... |
| สตริงว่าง | ROOT.war | /... |
ตามตัวอย่างในเอกสาร ยังมีรูปแบบระบุเวอร์ชันด้วย ## เช่น
foo##42.war สำหรับ context path /foo เวอร์ชัน 42
หากเปลี่ยนชื่อไฟล์ WAR ต้องแก้ปลายทางในกฎ api-to-backend ของส่วนที่ 06 ให้ตรงกัน
เช่นเปลี่ยนเป็น ROOT.war ปลายทางจะกลายเป็น
http://127.0.0.1:8080/{R:1} โดยไม่มีส่วน /corp-api
นี่คือข้อดีของการแยกสองค่านี้ออกจากกัน — แก้ที่ไฟล์เดียวโดยไม่ต้องแตะโค้ด
เอกสารของ Tomcat ระบุว่า ไม่แนะนำให้วางองค์ประกอบ
<Context> ไว้ใน server.xml โดยตรง
เพราะทำให้การแก้ไขค่ามีผลกระทบมากขึ้น เนื่องจากไฟล์ conf/server.xml
ไม่สามารถโหลดใหม่ได้โดยไม่รีสตาร์ท Tomcat
อีกทั้งค่า Context เริ่มต้นจะเขียนทับค่าที่กำหนดไว้ตรงนั้น
เอกสารยังระบุว่าแอตทริบิวต์ path
ต้องใช้เฉพาะเมื่อกำหนด Context แบบคงที่ใน server.xml เท่านั้น
ในกรณีอื่นค่า path จะถูกอนุมานจากชื่อไฟล์ให้เอง
จำกัดการรับฟังของ Tomcat
ส่วนที่ 03 และ 07 ตั้งอยู่บนเงื่อนไขว่า upstream เข้าถึงได้จาก IIS เท่านั้น
เอกสารของ HTTP Connector ระบุว่าแอตทริบิวต์ address
ใช้กำหนดว่าจะรับฟังที่ที่อยู่ใด และ
โดยค่าเริ่มต้น connector จะรับฟังทุกที่อยู่ในเครื่อง
การจำกัดขอบเขตจึงต้องระบุแอตทริบิวต์นี้เอง
<Connector port="8080" protocol="HTTP/1.1" address="127.0.0.1" />
เอกสารอธิบายเฉพาะหน้าที่ของแอตทริบิวต์นี้ ไม่ได้ยกตัวอย่างการใช้
127.0.0.1 เพื่อความปลอดภัยไว้โดยตรง ค่าดังกล่าวจึงเป็นการนำแอตทริบิวต์
มาใช้ให้ตรงกับข้อกำหนดด้านความปลอดภัยในส่วนที่ 07 ของเอกสารฉบับนี้
การรับค่า header ที่ IIS ส่งมา
ส่วนที่ 07 อธิบายว่า IIS ส่งข้อมูล client ต่อมาในรูป header
ฝั่ง Spring Boot ต้องถูกกำหนดค่าให้นำ header เหล่านั้นมาใช้ด้วย มิฉะนั้นจะถูกละเลย
เอกสารระบุ property ชื่อ server.forward-headers-strategy ซึ่งรับค่าได้สามแบบ
| ค่า | พฤติกรรมตามเอกสาร |
|---|---|
| NATIVE | ตัว web server จัดการ header forwarded เอง เอกสารระบุว่าการตั้งค่านี้
เพียงพอสำหรับ header ที่ใช้กันทั่วไปคือ X-Forwarded-For
และ X-Forwarded-Proto |
| FRAMEWORK | ใช้ตัวกรองของ Spring Framework จัดการแทน คือ ForwardedHeaderFilter
สำหรับ servlet stack และ ForwardedHeaderTransformer สำหรับ reactive stack |
| NONE | ไม่ประมวลผล header forwarded |
server.forward-headers-strategy=NATIVE
เอกสารของ Spring Boot เตือนไว้เมื่อกล่าวถึงการตั้งค่า proxy ของ Tomcat ว่า
สามารถเชื่อถือ proxy ทุกตัวได้ด้วยการตั้ง internal-proxies
เป็นค่าว่าง แต่ห้ามทำเช่นนั้นในระบบที่ใช้งานจริง
เนื่องจากจะเปิดช่องให้ client ปลอมแปลงหมายเลข IP และ header forwarded ได้
ข้อนี้สอดคล้องกับข้อกำหนดสองข้อในส่วนที่ 07 คือ proxy ต้องเขียนทับ header เสมอ และปลายทางต้องเข้าถึงได้จาก proxy เท่านั้น
สรุปค่าที่ต้องตรงกันทั้งสองฝั่ง
| รายการ | ฝั่ง IIS | ฝั่ง Spring Boot และ Tomcat |
|---|---|---|
| port ปลายทาง | ปลายทางในกฎ rewrite (ส่วน 06) | port ของ Connector |
| path ปลายทาง | /corp-api ในกฎ rewrite | ชื่อไฟล์ WAR |
| ขอบเขตการเข้าถึง | เรียกผ่าน 127.0.0.1 | address ของ Connector |
| ข้อมูล client | header forwarded (ส่วน 07) | server.forward-headers-strategy |
หากค่าใดค่าหนึ่งในตารางไม่ตรงกัน อาการที่พบจะตรงกับรายการในตารางวิเคราะห์ปัญหาของส่วนที่ 09
Tomcat และการพัฒนาแบบแยกส่วน
upstream คืออะไร และตอนพัฒนาต่างจากตอนใช้งานจริงอย่างไร
ตลอดส่วนที่ 01 ถึง 09 เราเรียกปลายทางว่า upstream โดยไม่ระบุว่าเป็นอะไร ส่วนนี้อธิบายว่าเมื่อปลายทางคือ Tomcat แล้วมันคืออะไร ทำงานอย่างไร และเหตุใดสภาพแวดล้อมตอนพัฒนาจึงต่างจากตอนใช้งานจริง
Tomcat คืออะไร
เอกสารของ Apache นิยาม Tomcat ว่าเป็น ซอฟต์แวร์โอเพนซอร์สที่ implement เทคโนโลยี Jakarta EE บางส่วน พูดให้ตรงกับบริบทของเอกสารฉบับนี้คือ มันทำหน้าที่แบบเดียวกับ IIS แต่สำหรับโค้ด Java
| บทบาท | IIS | Tomcat |
|---|---|---|
| รับ request จากเครือข่าย | ทำได้ | ทำได้ |
| ส่งไฟล์ static | ทำได้ | ทำได้ |
| เรียกใช้โค้ดแอปพลิเคชัน | .NET | Java |
| หน่วยที่นำขึ้นเซิร์ฟเวอร์ | โฟลเดอร์ + web.config | ไฟล์ .war |
| ไฟล์ตั้งค่าหลัก | applicationHost.config | server.xml |
IIS ไม่รู้จัก Tomcat เลย — สิ่งที่ IIS เห็นคือ
http://127.0.0.1:8080 ซึ่งเป็นปลายทาง HTTP ธรรมดา
ไม่ได้รับรู้ว่าอีกฝั่งเป็น Tomcat, Node หรืออย่างอื่น
นั่นคือเหตุผลที่เนื้อหาส่วนที่ 01 ถึง 09 ใช้ได้โดยไม่ขึ้นกับเทคโนโลยีของปลายทาง
ทำไมต้องเป็นเวอร์ชัน 11
ความต่างระหว่างเวอร์ชันไม่ใช่แค่ความใหม่ แต่คือ สเปกที่ implement และ เวอร์ชัน Java ขั้นต่ำ ซึ่งเป็นข้อจำกัดแบบตายตัว
| Tomcat | รุ่นล่าสุด | Java ที่รองรับ | Servlet |
|---|---|---|---|
| 11.0.x | 11.0.25 | 17 ขึ้นไป | 6.1 |
| 10.1.x | 10.1.59 | 11 ขึ้นไป | 6.0 |
| 9.0.x | 9.0.121 | 8 ขึ้นไป | 4.0 |
เอกสารระบุว่า Tomcat 11.0.x เป็นสายที่อยู่ระหว่างการพัฒนาหลักในปัจจุบัน พัฒนาต่อจาก 10.1.x และ implement Servlet 6.1 · JSP 4.0 · EL 6.0 · WebSocket 2.2 · Authentication 3.1 ซึ่งเป็นชุดที่แพลตฟอร์ม Jakarta EE 11 กำหนด
เอกสารของ Spring Boot ระบุ servlet container ที่รองรับไว้ว่า Tomcat 11.0.x ที่ Servlet 6.1 และเขียนไว้ด้วยว่าสามารถ deploy ไปยัง container ใดก็ได้ที่รองรับ Servlet 6.1 ขึ้นไป
อีกด้านหนึ่ง เอกสารระบุว่า Spring Boot รุ่นปัจจุบัน ต้องการ Java อย่างน้อย 17 และใช้ได้ถึง Java 26 พร้อมกับ Spring Framework 7.0.9 ขึ้นไป ตัวเลขทั้งหมดนี้จึงล็อกกันเป็นชุดเดียว หากเซิร์ฟเวอร์เป็น Tomcat 10.1 ซึ่งอยู่ที่ Servlet 6.0 จะใช้ Spring Boot รุ่นนี้ไม่ได้
webapps เพียงอย่างเดียวทำให้เกิด context path ขึ้นมา
ซึ่งเป็นค่าที่ต้องตรงกับกฎ rewrite ฝั่ง IIS
การนำ WAR ขึ้นเซิร์ฟเวอร์
เอกสารของ Tomcat ระบุว่า เมื่อแอตทริบิวต์ autoDeploy ของ Host เป็น
true — ซึ่งเป็นค่าเริ่มต้น — Host
จะพยายาม deploy และปรับปรุงแอปพลิเคชันให้เองแบบพลวัต
เช่นเมื่อมีไฟล์ .WAR ใหม่ถูกวางลงใน appBase
ครอบคลุมทั้งการ deploy ครั้งแรกและการ deploy ซ้ำของแอปที่เคยติดตั้งแล้ว
หมายความว่าการนำขึ้นระบบในกรณีปกติคือ คัดลอกไฟล์ .war
ลงใน webapps โดยไม่ต้องรีสตาร์ท Tomcat
ต่างจากฝั่ง IIS ที่การเปลี่ยน web.config มีผลทันทีเช่นกัน
ทั้งสองฝั่งจึงไม่ต้องหยุดบริการเพื่ออัปเดตตามปกติ
การทำให้ Tomcat ทำงานเป็น Windows Service
เพื่อให้ Tomcat เริ่มทำงานเองเมื่อเปิดเครื่อง ต้องติดตั้งเป็น service
เอกสารระบุว่าใช้สคริปต์ service.bat และ
ต้องมีสิทธิ์ระดับ Administrator จึงจะรันสคริปต์นี้ได้
service.bat install
:: หรือระบุชื่อ service เองได้
service.bat install MyService
เอกสารระบุว่า Tomcat11w เป็นโปรแกรมแบบกราฟิกสำหรับ
เฝ้าดูและกำหนดค่า Tomcat service · ส่วนไฟล์ catalina.out
จะรับทุกอย่างที่ถูกเขียนลง System.out และ System.err
ซึ่งรวมถึง exception ที่ไม่ถูกดักจับ จึงเป็นไฟล์แรกที่ควรเปิดดูเมื่อแอปไม่ทำงาน
ตามตารางในส่วนที่ 09
/api เหมือนเดิมทั้งสองกรณี
เหตุผลที่แยก frontend กับ backend ตอนพัฒนา
บนเครื่องของผู้พัฒนา ทั้งสองฝั่งรันเป็นคนละ process และคนละ port เอกสารของ Vite ระบุว่า port เริ่มต้นของ dev server คือ 5173 และหาก port นั้นถูกใช้อยู่แล้ว Vite จะลองหา port ว่างถัดไปให้เอง ส่วนฝั่ง Spring Boot รันแยกต่างหากที่ port ของตัวเอง
การแยกเช่นนี้ทำให้แต่ละฝั่ง รีโหลดตัวเองได้โดยไม่กระทบอีกฝั่ง
ฝั่ง frontend เห็นผลทันทีเมื่อแก้ไฟล์ ส่วนฝั่ง backend
เอกสาร Spring Boot ระบุว่าแอปที่ใช้ spring-boot-devtools
จะรีสตาร์ทอัตโนมัติเมื่อไฟล์บน classpath เปลี่ยนแปลง
ซึ่งเอกสารอธิบายว่าเป็นประโยชน์เมื่อทำงานใน IDE เพราะได้ผลตอบกลับที่รวดเร็วมาก
กุญแจอยู่ที่การใช้ path เดียวกันทั้งสองฝั่ง คือ /api
โดยตอนพัฒนาให้ server.proxy ของ Vite เป็นผู้ส่งต่อ
และตอนใช้งานจริงให้กฎ rewrite ของ IIS เป็นผู้ส่งต่อ
โค้ด frontend จึงไม่ต้องมีเงื่อนไขแยกตามสภาพแวดล้อม
| ประเด็น | ตอนพัฒนา | ตอนใช้งานจริง |
|---|---|---|
| ผู้ให้บริการไฟล์ frontend | Vite dev server | IIS |
| ผู้ส่งต่อ /api | server.proxy | กฎ rewrite + ARR |
| Tomcat ที่ใช้ | ที่ฝังมากับ Spring Boot | ติดตั้งแยกบนเครื่อง |
| รูปแบบที่ส่งมอบ | ไม่มี — รันจากซอร์ส | ไฟล์ static และ .war |
| การเห็นผลหลังแก้โค้ด | ทันที · devtools รีสตาร์ทให้ | ต้อง build และนำขึ้นใหม่ |
สิ่งที่ทำงานได้บนเครื่องพัฒนาอาจไม่ทำงานบนเซิร์ฟเวอร์ เพราะ ตอนพัฒนาไม่มี IIS อยู่ในเส้นทางเลย กฎ rewrite ทั้งหมดในส่วนที่ 06 · การกำหนด base path ในส่วนที่ 12 · การรับ header forwarded ในส่วนที่ 07 และ 10 · รวมถึงชนิดไฟล์ที่ IIS ไม่รู้จักในส่วนที่ 06 ล้วนเป็นประเด็นที่ปรากฏเฉพาะตอนใช้งานจริงทั้งสิ้น
การเตรียม React ให้ทำงานกับ IIS
ผลลัพธ์ของการ build และค่าที่ต้องตรงกับการตั้งค่า IIS
ส่วนนี้จำกัดเฉพาะจุดที่ frontend มาบรรจบกับ IIS คือ ไฟล์ที่ได้จากการ build วางอย่างไร และ ค่าใดต้องตรงกับกฎที่ตั้งไว้ในส่วนที่ 06 มิได้ครอบคลุมการเขียนคอมโพเนนต์หรือการออกแบบหน้าจอ
web.config
เขียนขึ้นครั้งเดียวและต้องอยู่ต่อไป
ผลลัพธ์ของการ build คือไฟล์ static ล้วน
เอกสารของ Vite ระบุว่าเมื่อสั่ง npm run build
ผลลัพธ์จะถูกวางไว้ที่โฟลเดอร์ dist โดยค่าเริ่มต้น
และสามารถนำโฟลเดอร์นี้ไป deploy บนแพลตฟอร์มใดก็ได้
เนื้อหาภายในประกอบด้วยไฟล์ HTML หนึ่งไฟล์ กับไฟล์ JavaScript และ CSS
ที่เอกสารเรียกว่า hashed assets
เพราะไม่มีไฟล์ใดต้องผ่านตัวแปลภาษาฝั่งเซิร์ฟเวอร์เลย งานของ IIS จึงเป็นเพียงการอ่านไฟล์จากดิสก์แล้วส่งออกไปตามชนิดที่ประกาศไว้ ซึ่งตรงกับหน้าที่ประการแรกในส่วนที่ 03 และเป็นเหตุผลที่ Application Pool ตั้งเป็น No Managed Code ได้ตามส่วนที่ 05
ก่อนนำขึ้นเซิร์ฟเวอร์ สามารถทดสอบผลลัพธ์ในเครื่องได้ด้วย npm run preview
ซึ่งเอกสารระบุว่าจะเปิดเว็บเซิร์ฟเวอร์แบบ static ในเครื่องเพื่อเสิร์ฟไฟล์จาก dist
ขั้นนี้ช่วยแยกได้ว่าปัญหาเกิดจากตัวไฟล์เองหรือเกิดจากการตั้งค่า IIS
เหตุผลที่ต้องมีกฎ fallback ใน web.config
กฎ spa-fallback ในส่วนที่ 06 ไม่ใช่เทคนิคเฉพาะของ IIS
แต่เป็นข้อกำหนดของแอปพลิเคชันแบบหน้าเดียวทุกตัว เอกสารของ React Router ระบุไว้ว่า
เป็นเรื่องปกติของการ deploy SPA ทุกแบบที่ต้องกำหนดค่าให้โฮสต์
ส่งทุก URL ไปยัง index.html ของผลลัพธ์ฝั่ง client
พร้อมระบุว่าโฮสต์บางรายทำให้โดยค่าเริ่มต้น แต่บางรายไม่ทำ
เอกสารของ React Router เขียนไว้ว่า หากได้รับสถานะ 404 ที่เส้นทางซึ่งถูกต้องของแอปพลิเคชัน มีความเป็นไปได้สูงว่าต้องไปกำหนดค่าที่โฮสต์ ซึ่งตรงกับแถวแรกของตารางวิเคราะห์ปัญหาในส่วนที่ 09 ของเอกสารฉบับนี้ และ IIS คือโฮสต์ที่ต้องกำหนดค่านั้น
หากใช้ React Router ในโหมด framework ที่เปิด SPA Mode
เอกสารระบุว่าให้ deploy โฟลเดอร์ build/client แทน
ชื่อโฟลเดอร์จึงขึ้นกับเครื่องมือที่ใช้ ให้ตรวจจากผลลัพธ์จริงก่อนคัดลอกขึ้นเซิร์ฟเวอร์
base path ต้องตรงกับตำแหน่งที่วาง
ลิงก์ของไฟล์ JavaScript และ CSS ที่ปรากฏใน index.html
ถูกกำหนดตั้งแต่ขั้นตอน build ไม่ใช่ตอนเรียกใช้งาน
เอกสารของ Vite อธิบายค่า base ไว้ว่าเป็น
public base path ที่ใช้ทั้งตอนพัฒนาและตอน production
โดยรับค่าเป็น absolute URL pathname เช่น /foo/ · URL เต็ม ·
หรือสตริงว่างกับ ./ สำหรับการฝังไว้ในระบบอื่น
เอกสารระบุเพิ่มว่า หากนำโปรเจกต์ไป deploy ใต้ path ย่อย
เพียงกำหนดค่า base แล้ว path ของ asset ทั้งหมดจะถูกเขียนใหม่ให้เอง
ครอบคลุมทั้ง URL ของ asset ที่ import ผ่าน JavaScript, การอ้างอิงด้วย
url() ใน CSS และการอ้างอิง asset ในไฟล์ HTML
vite build --base=/my/public/path/
| ตำแหน่งที่วางบน IIS | ค่า base ตอน build |
|---|---|
| รากของ site ตามเอกสารฉบับนี้ https://app.corp.local/ |
/ (ค่าเริ่มต้น) |
| ใต้ Application ย่อยตามส่วนที่ 02 https://app.corp.local/corp/ |
/corp/ |
หากวางไว้ใต้ path ย่อยแต่ build ด้วยค่าเริ่มต้น เบราว์เซอร์จะร้องขอไฟล์จากรากของโดเมน
ซึ่งไม่ตรงกับตำแหน่งจริง ผลคือหน้าเว็บโหลดขึ้นแต่ไม่มีสไตล์และไม่ทำงาน
โดยไฟล์ JavaScript และ CSS ได้รับสถานะ 404 ทั้งหมด
อาการนี้ต่างจากกรณี 404.3 ในส่วนที่ 06 ตรงที่ path ผิด ไม่ใช่ชนิดไฟล์ไม่รู้จัก
วิธีนำไฟล์ขึ้นเซิร์ฟเวอร์
เอกสารของ Vite เรียกไฟล์ผลลัพธ์ว่า hashed assets กล่าวคือชื่อไฟล์ผูกกับเนื้อหา จึงเปลี่ยนไปทุกครั้งที่ build ใหม่ คุณสมบัตินี้มีผลโดยตรงต่อวิธีคัดลอกไฟล์ขึ้นเซิร์ฟเวอร์
ลบเนื้อหาเดิมในโฟลเดอร์ปลายทางก่อน
เนื่องจากชื่อไฟล์เปลี่ยนทุกครั้ง การคัดลอกทับจึงไม่ได้ลบไฟล์รุ่นเก่าออก และจะสะสมค้างอยู่เรื่อย ๆ จนแยกไม่ออกว่าไฟล์ใดยังถูกใช้งาน
คัดลอกเนื้อหาของโฟลเดอร์ผลลัพธ์ทั้งชุด
วางลงที่ D:\apps\corp-web โดยให้ index.html
อยู่ที่รากของโฟลเดอร์ ตรงกับ physical path ของ site ตามส่วนที่ 05
คง web.config ไว้
ไฟล์ web.config ไม่ได้มาจากการ build จึงต้องไม่ถูกลบไปพร้อมไฟล์เดิม
หากลบไป กฎ rewrite ทั้งหมดในส่วนที่ 06 จะหายไปด้วย
และระบบจะกลับไปมีอาการ 404 ที่หน้าย่อยทันที
proxy ตอนพัฒนา ไม่ใช่ตัวเดียวกับ IIS
บนเครื่องของผู้พัฒนาไม่มี IIS คอยส่งต่อ /api ให้
เครื่องมือพัฒนาจึงมีกลไกของตัวเองมาแทน เอกสารของ Vite ระบุว่าค่า
server.proxy ใช้ กำหนดกฎ proxy สำหรับ dev server
โดยคำขอที่ path ขึ้นต้นด้วยคีย์ที่ระบุจะถูกส่งต่อไปยังปลายทางที่กำหนด
ค่านี้มีผล เฉพาะกับ dev server เท่านั้น ตามที่เอกสารระบุ เมื่อ build เป็นไฟล์ static แล้วจะไม่มีผลใด ๆ อีก ตัวส่งต่อใน production คือ IIS เสมอ ตามกฎในส่วนที่ 06
ข้อดีของการตั้งคีย์ให้ตรงกันทั้งสองฝั่ง คือโค้ด frontend เรียก /api/...
ได้เหมือนกันทั้งตอนพัฒนาและตอนใช้งานจริง โดยไม่ต้องมีเงื่อนไขแยกตามสภาพแวดล้อม
สรุปค่าที่ต้องตรงกันทั้งสองฝั่ง
| รายการ | ฝั่ง IIS | ฝั่ง React |
|---|---|---|
| ตำแหน่งไฟล์ | physical path ของ site (ส่วน 05) | เนื้อหาของโฟลเดอร์ผลลัพธ์ |
| path ที่ให้บริการ | Site หรือ Application (ส่วน 02) | ค่า base ตอน build |
| client-side routing | กฎ spa-fallback (ส่วน 06) | เส้นทางที่ router กำหนด |
| คำนำหน้าของ API | รูปแบบในกฎ rewrite (ส่วน 06) | คีย์ของ proxy ตอนพัฒนา |
Full Stack Architecture
ร้อยทุกส่วนที่ผ่านมาเข้าด้วยกัน — React, IIS, Tomcat และ Spring Boot ในเส้นทางเดียว
ส่วนที่ 01 ถึง 12 อธิบายแต่ละชิ้นแยกกันว่าคืออะไรและตั้งค่าอย่างไร ส่วนนี้นำทั้งหมดมาประกอบเป็นภาพเดียว เพื่อตอบสามคำถามที่เหลือ คือ สถาปัตยกรรมแบบนี้เรียกว่าอะไร · ทำไมถึงเลือกแบบนี้ · และค่าใดบ้างที่ต้องตรงกันตลอดเส้นทาง
สถาปัตยกรรมแบบนี้เรียกว่าอะไร
รูปแบบที่เราใช้มีชื่อเรียกอยู่แล้ว การรู้ชื่อมีประโยชน์เวลาค้นหาข้อมูลเพิ่มเติม หรือคุยกับคนนอกทีม เพราะคำเหล่านี้เป็นคำที่ใช้กันทั่วไปในวงการ
- Decoupled architectureบางที่เรียก HEADLESS
- การแยก frontend กับ backend ออกเป็นคนละโปรเจกต์ที่ build และ deploy แยกกัน โดยสื่อสารกันผ่าน HTTP เท่านั้น ทั้งสองฝั่งไม่รู้จักโค้ดของกันและกันเลย รู้เพียงว่าต้องเรียก URL ไหนและจะได้ข้อมูลรูปแบบใดกลับมา
- Client-side renderingCSR
- หน้าจอถูกประกอบขึ้นในเบราว์เซอร์ของผู้ใช้ ไม่ได้ประกอบมาจากเซิร์ฟเวอร์ สิ่งที่เซิร์ฟเวอร์ส่งไปคือไฟล์ JavaScript กับ HTML เปล่า ๆ หนึ่งไฟล์ ตามที่อธิบายไว้ในส่วนที่ 12
- Reverse proxyIIS ทำหน้าที่นี้
- ตัวกลางที่รับ request แทนเซิร์ฟเวอร์จริง แล้วส่งต่อให้เบื้องหลัง ผู้ใช้เห็นแค่ปลายทางเดียวคือ IIS ไม่รู้ว่าข้างหลังมีอะไรอยู่กี่ตัว
- Same-origin deploymentผลที่ได้
- เมื่อทั้งไฟล์หน้าเว็บและ API อยู่ใต้โดเมนเดียวกัน เบราว์เซอร์ถือว่าเป็นต้นทางเดียวกัน จึงไม่ต้องตั้งค่า CORS และ cookie ทำงานได้ตามปกติ ซึ่งเป็นเหตุผลสำคัญข้อหนึ่งที่วาง IIS ไว้ด้านหน้า
ทำไมถึงเลือกแบบนี้
ทางเลือกอื่นมีอยู่ และไม่ได้แย่กว่าเสมอไป แต่ในเงื่อนไขของงานนี้ Decoupled ได้เปรียบ ด้วยเหตุผลสี่ข้อ
| เงื่อนไขของงาน | ทำไม Decoupled ถึงเข้าทาง |
|---|---|
| ระบบรันบน Windows Server จึงมี IIS ติดมาอยู่แล้ว | สถาปัตยกรรมนี้ต้องการ reverse proxy อยู่แล้ว จึงใช้ web server ที่มากับระบบปฏิบัติการได้พอดี ไม่ต้องติดตั้งตัวกลางเพิ่ม และ IIS เองไม่ต้องรันโค้ดใดเลย |
| เป็นการรื้อหน้าจอใหม่ทั้งหมด | ช่วงแรกหน้าจอจะถูกแก้บ่อยกว่า backend มาก การแยกทำให้แก้หน้าจอแล้วคัดลอกไฟล์ทับได้เลย ไม่ต้อง build ฝั่ง Java ใหม่ |
| เป็นระบบภายในองค์กร | ข้อเสียหลักของ CSR คือ SEO และความเร็วตอนเปิดครั้งแรก ซึ่งแทบไม่มีผลกับระบบที่พนักงานเปิดใช้ประจำ |
| ทีมแบ่งงานกันได้ | คนทำ frontend กับ backend ทำงานคนละ repository และคนละจังหวะได้ ไม่ต้องรอกัน |
ไม่มีสถาปัตยกรรมใดได้มาฟรี สามข้อนี้คือสิ่งที่แลกไปและต้องระวังตลอดอายุของระบบ
- มีของต้องดูแลสองชิ้น — IIS site กับ Tomcat service เวลามีปัญหาต้องแยกให้ออกก่อนว่าอยู่ชั้นไหน ใช้วิธีในส่วนที่ 09
- มีค่าที่ต้องตรงกันหกคู่ — ผิดคู่เดียวก็ใช้งานไม่ได้ ดูตารางท้ายส่วนนี้
- เครื่อง dev ไม่เหมือนเซิร์ฟเวอร์จริง — ตอนพัฒนาไม่มี IIS อยู่ในเส้นทางเลย บั๊กบางประเภทจึงโผล่เฉพาะตอนขึ้นระบบ ซึ่งเป็นข้อที่กัดจริงที่สุดในทางปฏิบัติ
เส้นทางของ request ตามลำดับเวลา
แผนภาพด้านล่างอ่านจากซ้ายไปขวาตามเวลา แต่ละแถวคือผู้เล่นหนึ่งราย ตัวเลขคือลำดับเหตุการณ์ สังเกตความต่างของสองช่วง — ช่วงโหลดหน้าเว็บจบที่ IIS ไม่ลงไปถึง Tomcat เลย
ใครรับผิดชอบช่วงไหน
| ส่วนประกอบ | ทำงานที่ | หน้าที่ | ส่วนที่ |
|---|---|---|---|
| React | เบราว์เซอร์ของผู้ใช้ | แสดงหน้าจอ จัดการ client-side routing เรียก API | 12 |
| IIS | Windows Server | รับ TLS · ส่งไฟล์ static · ส่งต่อ /api · บันทึก log | 01–09 |
| Tomcat | Windows Server | รับ request จาก IIS · รันไฟล์ WAR · จัดการ servlet | 11 |
| Spring Boot | ภายใน Tomcat | ตรรกะของระบบ · เชื่อมฐานข้อมูล | 10 |
เส้นทางของ request สองแบบ
ทุก request ที่เข้ามาจะเดินตามหนึ่งในสองเส้นทางนี้เสมอ
ตัวที่ตัดสินคือกฎใน web.config ตามส่วนที่ 06
| ขั้น | เปิดหน้าเว็บ | เรียกข้อมูล |
|---|---|---|
| 1 | เบราว์เซอร์ส่ง request มาที่ https://app.corp.local |
|
| 2 | HTTP.sys รับไว้แล้วส่งให้ w3wp.exe ตามลำดับในส่วนที่ 01 |
|
| 3 | path ไม่ขึ้นต้นด้วย /api |
path ขึ้นต้นด้วย /api |
| 4 | ไม่ตรงไฟล์จริง จึงส่ง index.html |
เขียน URL ใหม่เป็น 127.0.0.1:8080/corp-api/… |
| 5 | React เริ่มทำงานในเบราว์เซอร์ | Tomcat ส่งต่อให้ Spring Boot ประมวลผล |
| 6 | React เรียก /api ต่อ — วนกลับไปคอลัมน์ขวา |
ส่งข้อมูลกลับผ่าน IIS ถึงเบราว์เซอร์ |
ค่าที่ต้องตรงกันตลอดเส้นทาง
นี่คือรายการที่รวบรวมจากทุกส่วน หากค่าใดไม่ตรงกัน อาการที่พบจะตรงกับตารางในส่วนที่ 09
| ค่า | กำหนดที่ | ต้องตรงกับ |
|---|---|---|
| คำนำหน้า API | โค้ด React เรียก /api | รูปแบบในกฎ rewrite · ส่วน 06 |
| port ปลายทาง | ปลายทางในกฎ rewrite | port ของ Tomcat Connector |
| path ปลายทาง | /corp-api ในกฎ rewrite | ชื่อไฟล์ .war · ส่วน 11 |
| ตำแหน่งที่ให้บริการ | Site หรือ Application · ส่วน 02 | ค่า base ตอน build · ส่วน 12 |
| ขอบเขตการเข้าถึง | IIS เรียกผ่าน 127.0.0.1 | address ของ Connector · ส่วน 11 |
| ข้อมูล client | header ที่ IIS ส่ง · ส่วน 07 | forward-headers-strategy · ส่วน 10 |
สิ่งที่ทำให้ทั้งระบบทำงานร่วมกันได้ ไม่ใช่เทคโนโลยีใดเทคโนโลยีหนึ่ง แต่คือ การที่ทุกฝั่งตกลงกันที่ค่าเดียวกัน IIS ไม่รู้ว่าปลายทางเป็น Tomcat · Tomcat ไม่รู้ว่า frontend เป็น React · React ไม่รู้ว่ามี IIS อยู่ตรงกลาง
แต่ละฝั่งรู้เพียงสัญญาที่ตกลงไว้ คือ path port และ header ซึ่งเป็นเหตุผลที่เปลี่ยนเทคโนโลยีฝั่งใดฝั่งหนึ่งได้โดยไม่ต้องรื้อทั้งระบบ
อภิธานศัพท์
คำที่ปรากฏตลอดเอกสาร รวบรวมไว้ที่เดียวเพื่อใช้เปิดย้อนดูระหว่างอ่าน
คอลัมน์สุดท้ายบอกว่าคำนั้นถูกอธิบายอย่างละเอียดอยู่ในส่วนใด หากเจอคำที่ไม่คุ้นระหว่างอ่าน ให้กลับมาดูที่นี่ก่อน
องค์ประกอบของ IIS
| คำ | ความหมาย | ส่วนที่ |
|---|---|---|
| HTTP.sys | ตัวรับฟัง port 80 และ 443 จริง ทำงานในเคอร์เนลของ Windows ไม่ใช่โปรแกรมที่เปิดได้เอง | 01 |
| W3SVC | บริการที่คอยตั้งค่าให้ HTTP.sys และแจ้ง WAS เมื่อมี request เข้าคิว | 01 |
| WAS | บริการที่อ่านไฟล์ตั้งค่าแล้วสร้างหรือรีไซเคิล worker process | 01 |
| w3wp.exe | process ที่ประมวลผล request จริง หนึ่งตัวต่อหนึ่ง Application Pool | 01 |
| Site | หน่วยใหญ่สุดของ IIS มี binding และโฟลเดอร์รากเป็นของตัวเอง | 02 |
| Application | ขอบเขตภายใน Site ที่กำหนด Application Pool ของตัวเองได้ | 02 |
| Virtual Directory | การแมป URL ไปยังโฟลเดอร์ที่อยู่คนละที่ ใช้ pool ของ Application ที่ครอบอยู่ | 02 |
| Application Pool | ขอบเขตเชิง process แอปคนละ pool แยกหน่วยความจำและสิทธิ์กัน | 02 · 05 |
| Binding | ชุด IP port และชื่อโฮสต์ ที่กำหนดว่า request แบบไหนเข้ามาที่ Site นี้ | 02 · 08 |
| applicationHost.config | ไฟล์ตั้งค่าหลักของ IIS ทั้งเครื่อง | 02 |
| web.config | ไฟล์ตั้งค่าระดับโฟลเดอร์ เดินทางไปพร้อมไฟล์เมื่อคัดลอก | 02 · 06 |
| ApplicationPoolIdentity | บัญชีเสมือนที่ถูกสร้างพร้อม pool ใช้ชื่อรูปแบบ IIS AppPool\ชื่อ pool | 05 |
module และการส่งต่อ
| คำ | ความหมาย | ส่วนที่ |
|---|---|---|
| URL Rewrite | module เสริมที่ทำให้เขียนกฎแปลง URL ได้ ต้องติดตั้งก่อน ARR | 04 · 06 |
| ARR | Application Request Routing — module ที่ทำให้ IIS ส่ง request ต่อไปเซิร์ฟเวอร์อื่นได้ | 04 |
| upstream | เซิร์ฟเวอร์ที่อยู่หลัง reverse proxy และเป็นผู้ประมวลผล request จริง ในเอกสารนี้คือ Tomcat ที่ 127.0.0.1:8080 | 03 · 07 · 11 |
| Reverse proxy | ตัวกลางที่รับ request แทนเซิร์ฟเวอร์จริงแล้วส่งต่อให้เบื้องหลัง ผู้ใช้เห็นแค่ปลายทางเดียว | 03 · 13 |
| Rewrite | การเขียน URL ใหม่แล้วส่งต่อภายใน ผู้ใช้ไม่เห็นการเปลี่ยนแปลง | 06 |
| Redirect | การสั่งให้เบราว์เซอร์ไปเรียก URL ใหม่เอง แถบที่อยู่จะเปลี่ยนตาม | 06 · 08 |
| SPA fallback | กฎที่ส่ง index.html กลับไปเมื่อ path ที่ขอไม่ใช่ไฟล์จริง | 03 · 06 |
| MIME type | ชนิดของไฟล์ที่ IIS ต้องรู้จักก่อนจึงจะยอมส่งออก มิฉะนั้นได้ 404.3 | 06 |
| X-Forwarded-For | header ที่พา IP เดิมของผู้ใช้ไปให้ upstream เพราะ IP จริงหายเมื่อผ่าน proxy | 07 · 10 |
| SNI | ความสามารถที่ทำให้หลาย Site ใช้ใบรับรองต่างกันบน port 443 เดียวกันได้ | 08 |
| loopback | ที่อยู่ 127.0.0.1 ซึ่งวนกลับเข้าเครื่องตัวเอง ไม่ออกสู่เครือข่าย | 03 · 11 |
ฝั่ง Java และ Tomcat
| คำ | ความหมาย | ส่วนที่ |
|---|---|---|
| Servlet | มาตรฐานของ Java สำหรับเขียนโปรแกรมที่ตอบ request ทาง HTTP | 11 |
| Servlet container | โปรแกรมที่รัน servlet ให้ — Tomcat คือหนึ่งในนั้น เทียบได้กับบทบาทของ IIS ฝั่ง .NET | 11 |
| Jakarta EE | ชุดมาตรฐานของ Java ฝั่งเซิร์ฟเวอร์ Tomcat 11 implement เวอร์ชัน 11 ของชุดนี้ | 11 |
| WAR | ไฟล์แอปพลิเคชันของ Java ที่นำไปวางใน servlet container ที่ติดตั้งแยก | 10 · 11 |
| JAR | ไฟล์ที่มี servlet container ฝังอยู่ในตัวและรันเองได้ — ไม่ใช่รูปแบบที่ใช้ในเอกสารนี้ | 10 |
| context path | path นำหน้าที่ Tomcat ใช้แยกแต่ละแอป มาจากชื่อไฟล์ WAR โดยตรง | 11 |
| webapps | โฟลเดอร์ของ Tomcat ที่ใช้วางไฟล์ WAR | 11 |
| CATALINA_HOME | รากของการติดตั้ง Tomcat ที่เก็บไฟล์ที่ใช้ร่วมกัน | 11 |
| CATALINA_BASE | รากของค่าตั้งเฉพาะ instance ใช้เมื่อต้องการหลาย instance บนเครื่องเดียว | 11 |
| Connector | ส่วนของ Tomcat ที่กำหนดว่าจะรับฟัง port ใดและที่อยู่ใด | 11 |
| catalina.out | ไฟล์ที่รับทุกอย่างที่โปรแกรมเขียนออกทางหน้าจอ รวมถึง error ที่ไม่ถูกดักจับ | 11 |
| classpath | รายการตำแหน่งที่ JVM ใช้ค้นหาไฟล์โปรแกรม devtools เฝ้าดูที่นี่เพื่อรีสตาร์ทอัตโนมัติ | 11 |
ฝั่ง frontend และสถาปัตยกรรม
| คำ | ความหมาย | ส่วนที่ |
|---|---|---|
| SPA | Single Page Application — เว็บที่โหลด HTML ครั้งเดียวแล้วเปลี่ยนหน้าในเบราว์เซอร์เอง | 03 · 12 |
| CSR | Client-side rendering — หน้าจอถูกประกอบในเบราว์เซอร์ ไม่ได้ประกอบมาจากเซิร์ฟเวอร์ | 13 |
| build | ขั้นตอนแปลงซอร์สโค้ดเป็นไฟล์ที่นำขึ้นเซิร์ฟเวอร์ได้จริง | 12 |
| static file | ไฟล์ที่ส่งออกไปตามเนื้อหาเดิมทุกไบต์ ไม่ต้องผ่านการประมวลผล | 03 · 12 |
| hashed assets | ไฟล์ผลลัพธ์ที่ชื่อผูกกับเนื้อหา จึงเปลี่ยนชื่อทุกครั้งที่ build ใหม่ | 12 |
| base path | path นำหน้าที่ฝังอยู่ในลิงก์ของไฟล์ตั้งแต่ตอน build ต้องตรงกับตำแหน่งที่วางจริง | 12 |
| dev server | เซิร์ฟเวอร์ชั่วคราวบนเครื่องผู้พัฒนา ไม่มีอยู่บนเซิร์ฟเวอร์จริง | 11 · 12 |
| CORS | กติกาของเบราว์เซอร์ที่จำกัดการเรียกข้ามโดเมน — สถาปัตยกรรมนี้เลี่ยงได้เพราะอยู่โดเมนเดียวกัน | 03 · 13 |
| Same-origin | สภาพที่ไฟล์หน้าเว็บและ API อยู่ใต้โดเมนเดียวกันในสายตาเบราว์เซอร์ | 13 |
| Decoupled architecture | การแยก frontend กับ backend เป็นคนละโปรเจกต์ที่ build และ deploy แยกกัน | 13 |
| TLS | การเข้ารหัสระหว่างเบราว์เซอร์กับเซิร์ฟเวอร์ ในระบบนี้สิ้นสุดที่ IIS | 08 |
แหล่งอ้างอิง
เอกสารทุกรายการด้านล่างถูกเปิดอ่านเนื้อหาเต็มเพื่อตรวจสอบ มิใช่อ้างจากผลการค้นหา
สถาปัตยกรรมและองค์ประกอบ
| # | เรื่อง / แหล่ง |
|---|---|
| 01 | Introduction to IIS ArchitecturesMICROSOFT องค์ประกอบ HTTP.sys, W3SVC และ WAS, คุณประโยชน์สามประการของ HTTP.sys, บทบาทของ application pool และลำดับการประมวลผล request แปดขั้น learn.microsoft.com/en-us/iis/get-started/introduction-to-iis/introduction-to-iis-architecture |
| 02 |
Application Pool IdentitiesMICROSOFT
บัญชีเสมือนรูปแบบ IIS AppPool\<ชื่อ pool>, ค่าเริ่มต้น ApplicationPoolIdentity และตัวอย่างคำสั่ง ICACLS
learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities
|
Reverse proxy และ URL Rewrite
| # | เรื่อง / แหล่ง |
|---|---|
| 03 | Reverse Proxy with URL Rewrite v2 and Application Request RoutingMICROSOFT ข้อความว่าความสามารถ reverse proxy ถูกปิดเป็นค่าเริ่มต้น และรูปแบบกฎที่ใช้ในส่วนที่ 06 learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing |
| 04 |
URL Rewrite Module Configuration ReferenceMICROSOFT
นิยามของ IsFile, IsDirectory, negate, logicalGrouping, stopProcessing, back-reference {R:N}, ค่า redirectType และตัวแปร HTTPS ที่มีค่า OFF
learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference
|
| 05 | Creating Rewrite Rules for the URL Rewrite ModuleMICROSOFT ความแตกต่างระหว่าง action แบบ Rewrite และ Redirect และตำแหน่งที่เก็บกฎ learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/creating-rewrite-rules-for-the-url-rewrite-module |
| 06 |
ARR as generic proxy in Hotmail and SkyDriveMICROSOFT
เหตุผลของการตั้ง preserveHostHeader = true และการบันทึกสถานะ 502 ของ ARR
learn.microsoft.com/en-us/iis/extensions/configuring-application-request-routing-arr/arr-as-generic-proxy-in-hotmail-and-skydrive
|
| 07 | URL RewriteMICROSOFT หน้าดาวน์โหลดทางการ ระบุเวอร์ชัน 2.1 รองรับ IIS 7 ถึง IIS 10 iis.net/downloads/microsoft/url-rewrite |
| 08 | Application Request RoutingMICROSOFT หน้าดาวน์โหลดทางการ ระบุเวอร์ชัน 3.0 และข้อกำหนดว่าต้องติดตั้ง URL Rewrite ก่อน iis.net/downloads/microsoft/application-request-routing |
เนื้อหาแบบ static status code และ log
| # | เรื่อง / แหล่ง |
|---|---|
| 09 |
Adding Static Content MIME Mappings <mimeMap>MICROSOFT
ข้อความว่า IIS ไม่ส่งคืนชนิดไฟล์ที่ไม่ได้เพิ่มไว้ใน <staticContent> และรูปแบบการประกาศ
learn.microsoft.com/en-us/iis/configuration/system.webserver/staticcontent/mimemap
|
| 10 |
HTTP Status Code Overview (IIS)MICROSOFT
นิยามของ 404.3 MIME type restriction, 400.10 Invalid X-Forwarded-For header, 502 และรหัสย่อยของ ARR
learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/health-diagnostic-performance/http-status-code
|
| 11 |
Configure Logging in IISMICROSOFT
ตำแหน่งเริ่มต้นของ log IIS คือ %SystemDrive%\inetpub\logs\LogFiles
learn.microsoft.com/en-us/iis/manage/provisioning-and-managing-iis/configure-logging-in-iis
|
| 12 |
Configuring HTTP Server API Error LoggingMICROSOFT
โฟลเดอร์เริ่มต้น %SystemRoot%\System32\LogFiles พร้อมโฟลเดอร์ย่อย HTTPERR และ registry ที่ควบคุมการบันทึก
learn.microsoft.com/en-us/windows/win32/http/configuring-http-server-api-error-logging
|
| 13 |
Troubleshoot failed requests using tracing in IISMICROSOFT
หลักการเก็บ trace ไว้ในบัฟเฟอร์ ตำแหน่ง FailedReqLogFiles\W3SVC<id> และโครงสร้าง configuration
learn.microsoft.com/en-us/previous-versions/troubleshoot/iis/troubleshoot-failed-requests-using-tracing-in-iis-7
|
การยืนยันตัวตนและ cmdlet
| # | เรื่อง / แหล่ง |
|---|---|
| 14 |
Windows Authentication <windowsAuthentication>MICROSOFT
ลำดับสามขั้นของการเปิดใช้งาน ค่าเริ่มต้น enabled="false" คำสั่ง appcmd และข้อกำหนดเรื่อง /commit:apphost
learn.microsoft.com/en-us/iis/configuration/system.webserver/security/authentication/windowsauthentication/
|
| 15 |
Install-WindowsFeature (ServerManager)MICROSOFT
ข้อกำหนดด้านสิทธิ์ระดับยกระดับ และพารามิเตอร์ -IncludeManagementTools
learn.microsoft.com/en-us/powershell/module/servermanager/install-windowsfeature
|
| 16 |
New-WebAppPool, New-Website, New-WebBinding (WebAdministration)MICROSOFT
syntax ของ cmdlet, ค่า -SslFlags ทั้งสี่ค่า และตัวอย่างการผูกใบรับรองด้วย AddSslCertificate
learn.microsoft.com/en-us/powershell/module/webadministration/
|
Spring Boot และ Apache Tomcat · ส่วนที่ 10
| # | เรื่อง / แหล่ง |
|---|---|
| 17 |
Traditional DeploymentSpring BootSPRING
ขั้นตอนสร้างไฟล์ WAR ที่ deploy ได้ ทั้งการสืบทอด SpringBootServletInitializer, การเปลี่ยน packaging การประกาศ spring-boot-starter-tomcat เป็น provided และข้อจำกัดของ Spring WebFlux
docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html
|
| 18 |
Embedded Web ServersSpring BootSPRING
property server.forward-headers-strategy ค่า NATIVE, FRAMEWORK, NONE และคำเตือนเรื่องการเชื่อถือ proxy ทุกตัว
docs.spring.io/spring-boot/how-to/webserver.html
|
| 19 |
The Context ContainerApache Tomcat 11APACHE
กฎการอนุมาน context path จากชื่อไฟล์ WAR, กรณีพิเศษ ROOT, การใช้ # และข้อไม่แนะนำให้กำหนด Context ใน server.xml
tomcat.apache.org/tomcat-11.0-doc/config/context.html
|
| 20 |
The HTTP ConnectorApache Tomcat 11APACHE
แอตทริบิวต์ address และข้อความว่าโดยค่าเริ่มต้น connector รับฟังทุกที่อยู่ในเครื่อง
tomcat.apache.org/tomcat-11.0-doc/config/http.html
|
React และ Vite · ส่วนที่ 12
| # | เรื่อง / แหล่ง |
|---|---|
| 21 |
Building for ProductionViteVITE
หัวข้อ Public Base Path การเขียน path ของ asset ใหม่ตามค่า base และการเรียกไฟล์ผลลัพธ์ว่า hashed assets
vite.dev/guide/build
|
| 22 |
Deploying a Static SiteViteVITE
ข้อความว่าผลลัพธ์ของการ build ถูกวางที่ dist โดยค่าเริ่มต้น และคำสั่ง npm run preview สำหรับทดสอบในเครื่อง
vite.dev/guide/static-deploy
|
| 23 |
Shared OptionsViteVITE
นิยามของค่า base และรูปแบบค่าที่รับได้
vite.dev/config/shared-options
|
| 24 |
Server OptionsViteVITE
ค่า server.proxy ซึ่งเอกสารระบุว่าใช้กำหนดกฎ proxy สำหรับ dev server
vite.dev/config/server-options
|
| 25 |
Single Page App (SPA)React RouterREACT ROUTER
หัวข้อ Direct all URLs to index.html ข้อกำหนดให้โฮสต์ส่งทุก URL ไปยัง index.html และข้อความเรื่องอาการ 404 ที่เส้นทางซึ่งถูกต้อง
reactrouter.com/how-to/spa
|
Tomcat และการพัฒนา · ส่วนที่ 11
| # | เรื่อง / แหล่ง |
|---|---|
| 26 | Which Version Do I Want?Apache TomcatAPACHE ตารางเวอร์ชัน Tomcat กับสเปกและเวอร์ชัน Java ที่รองรับ และข้อความว่า Tomcat 11.0.x implement Servlet 6.1 ตามที่ Jakarta EE 11 กำหนด tomcat.apache.org/whichversion.html |
| 27 |
IntroductionApache Tomcat 11APACHE
นิยามของ Tomcat โครงสร้างไดเรกทอรี /bin /conf /logs /webapps และความต่างของ CATALINA_HOME กับ CATALINA_BASE
tomcat.apache.org/tomcat-11.0-doc/introduction.html
|
| 28 |
Deployer How ToApache Tomcat 11APACHE
พฤติกรรมของ autoDeploy เมื่อมีไฟล์ .WAR ถูกวางลงใน appBase
tomcat.apache.org/tomcat-11.0-doc/deployer-howto.html
|
| 29 |
Windows Service How ToApache Tomcat 11APACHE
สคริปต์ service.bat ข้อกำหนดเรื่องสิทธิ์ Administrator และโปรแกรม Tomcat11w
tomcat.apache.org/tomcat-11.0-doc/windows-service-howto.html
|
| 30 |
LoggingApache Tomcat 11APACHE
บทบาทของไฟล์ catalina.out ที่รับทุกอย่างซึ่งเขียนลง System.out และ System.err
tomcat.apache.org/tomcat-11.0-doc/logging.html
|
| 31 | System RequirementsSpring BootSPRING เวอร์ชัน Java ที่ต้องใช้ และรายการ servlet container ที่รองรับพร้อมเลขเวอร์ชัน Servlet docs.spring.io/spring-boot/system-requirements.html |
| 32 | Developer ToolsSpring BootSPRING การรีสตาร์ทอัตโนมัติเมื่อไฟล์บน classpath เปลี่ยนแปลง docs.spring.io/spring-boot/reference/using/devtools.html |
Knowledge Graph · หน้า ◆
| # | เรื่อง / แหล่ง |
|---|---|
| 33 | Linked DataDesign IssuesW3C กฎสี่ข้อของ Linked Data ตั้งแต่ “Use URIs as names for things” ถึง “Include links to other URIs. so that they can discover more things.” และ 5-star deployment scheme www.w3.org/DesignIssues/LinkedData.html |
| 34 | RDF 1.1 PrimerW3CW3C นิยามของ triple ว่า subject กับ object คือ resource สองตัวที่ถูกเชื่อม ส่วน predicate คือลักษณะของความสัมพันธ์ · ข้อความว่า IRI เป็น global identifier · และกลไกที่ resource เดียวกันไปอยู่ทั้งฝั่ง subject และ object ทำให้พบความเชื่อมโยงระหว่าง triple ได้ www.w3.org/TR/rdf11-primer/ |
| 35 | Graph database conceptsNeo4jNEO4J นิยามของ node, label, relationship, relationship type, property, traversal และ path ที่ใช้ในหน้า Knowledge Graph neo4j.com/docs/getting-started/appendix/graphdb-concepts/ |
| 36 | Weakly Connected ComponentsNeo4j Graph Data ScienceNEO4J อัลกอริทึมหาชุดของ node ที่เดินถึงกันได้ และข้อความว่าใช้ตัดสินได้ว่าเครือข่ายเชื่อมถึงกันทั้งหมดหรือไม่ neo4j.com/docs/graph-data-science/current/algorithms/wcc/ |
ชื่อ site path ของไฟล์ หมายเลข port และ path ปลายทางทั้งหมดในเอกสารนี้ เป็นค่าตัวอย่างที่กำหนดขึ้น มิใช่ค่าที่ตรวจวัดหรือดึงมาจากระบบจริง ส่วน Thumbprint ของใบรับรองในส่วนที่ 08 เป็นค่าตัวอย่างที่ปรากฏในเอกสารของ Microsoft
Knowledge Graph
ทุกสิ่งที่เอกสารนี้พูดถึง วางเป็น node เดียวกันทั้งฉบับ แล้วโยงด้วยความสัมพันธ์ที่มีชื่อ
เอกสารแบ่งเป็น 13 ส่วน แต่ละส่วนอธิบายคนละเรื่อง ผลข้างเคียงคือผู้อ่านเห็นทีละส่วน
แล้วไม่เห็นว่า context path ในส่วนที่ 11 เป็นค่าเดียวกับที่กฎ rewrite
ในส่วนที่ 06 ต้องชี้ไปหา หน้านี้จึงถอดเนื้อหาทั้งฉบับออกมาเป็นกราฟ
node คือสิ่งที่เอกสารพูดถึง เส้นคือความสัมพันธ์ที่มีชื่อกำกับ
วิธีที่ทำให้ข้อมูลถึงกันหมด
คำถามว่า “ทำอย่างไรให้ข้อมูลถึงกันหมด” มีคำตอบเป็นกฎที่เขียนไว้ในมาตรฐานจริง ไม่ใช่การลากเส้นให้มากที่สุด ห้าข้อด้านล่างคือกฎที่กราฟนี้ยึดและตรวจตามได้
| # | หลักการ | ข้อความจากเอกสารต้นทาง | ผลกับกราฟนี้ |
|---|---|---|---|
| 1 | หนึ่งสิ่ง ต้องมี identifier เดียว | “Use URIs as names for things”W3C | Tomcat 11 ที่พูดถึงในส่วนที่ 11 กับในส่วนที่ 13 เป็น node ตัวเดียวกัน ไม่แตกเป็นสองก้อน |
| 2 | identifier นั้นต้องเปิดตามไปดูได้ | “Use HTTP URIs so that people can look up those names.”W3C | ทุก node ผูกกับหมายเลขส่วนของเอกสาร กดแล้วรู้ว่าอ่านต่อได้ที่ไหน |
| 3 | เก็บทุกข้อเท็จจริงเป็น triple | “The subject and the object represent the two resources being related; the predicate represents the nature of their relationship.”W3C | ความสัมพันธ์ทั้ง 65 เส้นเขียนเป็น subject · predicate · object ได้ทั้งหมด |
| 4 | ทุกจุดต้องมีทางเดินออกไปหาจุดอื่น | “Include links to other URIs. so that they can discover more things.”W3C | ไม่มี node ใดในกราฟนี้ที่ไม่มีเส้นเลย จำนวนเส้นน้อยสุดต่อ node คือ 1 |
| 5 | ตรวจว่าเชื่อมถึงกันจริง ด้วย WCC | Weakly Connected Components “can be used to determine whether your network is fully connected or not”NEO4J | รันแล้วได้ 1 component ขนาด 54 node แปลว่าเดินจาก node ใดก็ถึงทุก node ที่เหลือ |
ไม่ใช่การลากเส้นตรงระหว่างสองสิ่ง แต่คือการที่ สิ่งเดียวกันไปปรากฏทั้งฝั่ง subject ของ triple หนึ่ง และฝั่ง object ของอีก triple หนึ่ง เอกสารของ W3C เขียนไว้ว่า “this ability to have the same resource be in the subject position of one triple and the object position of another makes it possible to find connections between triples”
subject predicate object WAR DEPLOYED_TO webapps Tomcat 11 SCANS webapps ^^^^^^^ resource เดียวกัน เดินกราฟ : WAR → webapps → Tomcat 11 path length = 2
node กับ relationship ต่างกันอย่างไร
นิยามที่ใช้ในหน้านี้มาจากเอกสาร property graph ของ Neo4j ซึ่งเป็นรูปแบบเดียวกับที่ graph database ใช้จริง
| คำ | นิยามตามเอกสาร | ในกราฟนี้คือ |
|---|---|---|
| node | “Nodes are used to represent entities (discrete objects) of a domain.” | 54 กล่องในภาพ |
| label | “Nodes can have zero or more labels to define (classify) what kind of nodes they are.” | หมวดสี่สี IIS · Java · React · สถาปัตยกรรม |
| relationship | “A relationship describes how a connection between a source node and a target node are related.” และ “A relationship must have exactly one relationship type” | 65 เส้น แต่ละเส้นมีชื่อเดียว เช่น PROXIES_TO |
| traversal | “Traversing a graph means visiting nodes by following relationships according to some rules.” | การไล่จาก browser ไปจนถึง catalina.out |
| path | ผลของการ traverse “A path containing one relationship has the length of 1” | WAR → webapps → Tomcat 11 คือ path ยาว 2 |
คลิกที่กล่องใดก็ได้ในภาพ เพื่อดูว่าสิ่งนั้นต่อกับอะไรบ้าง ด้วย relationship ชื่ออะไร และอยู่ในส่วนที่เท่าไรของเอกสาร · ชี้ที่เส้นเพื่ออ่านชื่อ relationship บนภาพ
รายการ relationship ทั้งหมด
ตารางนี้คือกราฟเดียวกับภาพด้านบน เขียนในรูป triple ทุกแถวคือหนึ่งเส้น ค้นคำใน predicate เพื่อหาความสัมพันธ์ชนิดเดียวกันได้ทั้งหมด
| subject | predicate | object | หมวด |
|---|---|---|---|
| Windows Server | SHIPS_WITH | IIS | สถาปัตยกรรม |
| IIS | HOSTS | Site | สถาปัตยกรรม |
| IIS | ACTS_AS | Reverse proxy | สถาปัตยกรรม |
| IIS | PROVIDES | Same-origin | สถาปัตยกรรม |
| browser | REQUESTS | HTTP.sys | สถาปัตยกรรม |
| browser | LOADS | dist | สถาปัตยกรรม |
| HTTP.sys | QUEUES_TO | w3wp.exe | IIS |
| HTTP.sys | LOGS_TO | httperr | IIS |
| W3SVC | READS | applicationHost.config | IIS |
| W3SVC | NOTIFIES | WAS | IIS |
| WAS | SPAWNS | w3wp.exe | IIS |
| w3wp.exe | BELONGS_TO | Application Pool | IIS |
| w3wp.exe | LOADS | URL Rewrite | IIS |
| w3wp.exe | LOADS | StaticFileModule | IIS |
| w3wp.exe | WRITES | IIS log | IIS |
| Application Pool | RUNS_AS | ApplicationPoolIdentity | IIS |
| applicationHost.config | DEFINES | Site | IIS |
| Site | HAS | Binding | IIS |
| Site | CONTAINS | Application | IIS |
| Application | CONTAINS | Virtual Directory | IIS |
| Application | ASSIGNED_TO | Application Pool | IIS |
| web.config | CONFIGURES | Site | IIS |
| web.config | CONTAINS | rewrite rule | IIS |
| URL Rewrite | EVALUATES | rewrite rule | IIS |
| ARR | REQUIRES | URL Rewrite | IIS |
| ARR | PROXIES_TO | Tomcat 11 | IIS |
| SPA fallback | IS_A | rewrite rule | IIS |
| StaticFileModule | SERVES | dist | IIS |
| StaticFileModule | CHECKS | mimeMap | IIS |
| Binding | TERMINATES | TLS | IIS |
| TLS | SELECTED_BY | SNI | IIS |
| Reverse proxy | ADDS | X-Forwarded-For | สถาปัตยกรรม |
| Reverse proxy | TARGETS | 127.0.0.1 | สถาปัตยกรรม |
| X-Forwarded-For | READ_BY | forward-headers-strategy | IIS |
| Failed Request Tracing | TRACES | w3wp.exe | IIS |
| Spring Boot | PACKAGED_AS | WAR | Java |
| Spring Boot | EXTENDS | SpringBootServletInitializer | Java |
| forward-headers-strategy | CONFIGURES | Spring Boot | Java |
| DevTools | RESTARTS | Spring Boot | Java |
| WAR | DEPLOYED_TO | webapps | Java |
| WAR | DERIVES | context path | Java |
| Tomcat 11 | SCANS | webapps | Java |
| Tomcat 11 | ENABLES | autoDeploy | Java |
| Tomcat 11 | LISTENS_ON | Connector | Java |
| Tomcat 11 | RUNS_ON | JVM | Java |
| Tomcat 11 | IMPLEMENTS | Servlet 6.1 | Java |
| Tomcat 11 | WRITES | catalina.out | Java |
| autoDeploy | WATCHES | webapps | Java |
| Connector | BINDS | 127.0.0.1 | Java |
| context path | MATCHED_BY | rewrite rule | Java |
| React | BUILT_BY | Vite | React |
| React | ROUTED_BY | React Router | React |
| React | RENDERS_VIA | CSR | React |
| Vite | OUTPUTS | dist | React |
| Vite | APPLIES | base path | React |
| Vite | SERVES_DEV | Vite dev server | React |
| dist | CONTAINS | hashed assets | React |
| server.proxy | CONFIGURES | Vite dev server | React |
| Vite dev server | FORWARDS_TO | Spring Boot | React |
| React Router | NEEDS | SPA fallback | React |
| Decoupled architecture | COMBINES | React | สถาปัตยกรรม |
| Decoupled architecture | COMBINES | Spring Boot | สถาปัตยกรรม |
| Decoupled architecture | REQUIRES | Reverse proxy | สถาปัตยกรรม |
| Same-origin | ELIMINATES | CORS | สถาปัตยกรรม |
| Vite dev server | WOULD_HIT | CORS | React |
การตรวจสอบกราฟนี้
ทุกบรรทัดในตารางนี้ได้จากการรันสคริปต์กับข้อมูลกราฟจริง มิใช่การประเมินด้วยสายตา
| เกณฑ์ | เหตุผลที่ต้องตรวจ | ผล |
|---|---|---|
| id ของ node ไม่ซ้ำ | ถ้าซ้ำ สิ่งเดียวกันจะกลายเป็นสอง node แล้วกราฟขาดตรงนั้น | 54 / 54 ไม่ซ้ำ |
| ปลายเส้นทุกเส้นชี้ไป id ที่มีอยู่จริง | เส้นที่ชี้ไป id ผิดจะกลายเป็นเส้นลอย | 65 / 65 ถูกต้อง |
| ไม่มี node ที่ไม่มีเส้นเลย | กฎข้อ 4 ของ Linked Data | degree ต่ำสุด = 1 |
| Weakly Connected Components | เกณฑ์ชี้ขาดว่าข้อมูลถึงกันหมดหรือไม่ | 1 component · ขนาด 54 |
| กล่องทับกัน | ข้อกำหนดการแสดงผลของเอกสารนี้ | 0 คู่ |
| เส้นพาดผ่านกล่องที่ไม่ใช่ปลายทางตัวเอง | ทำให้อ่านผิดว่าเส้นนั้นเชื่อมกับกล่องที่มันพาดผ่าน | 0 จุด |
ลองวางป้ายชื่อทั้ง 65 เส้นแล้วตรวจดู พบว่า 30 จาก 54 กรณีมีป้ายทับกันหรือทับกล่อง ซึ่งขัดกับข้อกำหนดของเอกสารนี้ จึงเปลี่ยนเป็นคำนวณตำแหน่งที่ว่างของแต่ละเส้นไว้ล่วงหน้า แล้วแสดงทีละเส้นตอนชี้ ส่วนภาพรวมของความสัมพันธ์ทั้งหมดอ่านได้จากตาราง triple ด้านบน