# เจาะโครงสร้าง IIS

> เอกสารความรู้สำหรับทีมพัฒนา — IIS, Spring Boot, Apache Tomcat 11 และ React
> แต่ละตัวคืออะไร ทำงานอย่างไร และรวมกันเป็นระบบเดียวได้อย่างไร
>
> ฉบับเว็บ: <https://mellowgeeuy.github.io/iis-knowladge/>

## สารบัญ

- **—** · [ภาพรวมและขอบเขต](#ภาพรวมและขอบเขต)
- **01** · [องค์ประกอบภายในของ IIS](#องค์ประกอบภายในของ-iis)
- **02** · [หน่วยการจัดการทั้งสี่](#หน่วยการจัดการทั้งสี่)
- **03** · [หน้าที่สองประการของ IIS](#หน้าที่สองประการของ-iis)
- **04** · [การติดตั้งองค์ประกอบที่จำเป็น](#การติดตั้งองค์ประกอบที่จำเป็น)
- **05** · [การสร้าง Application Pool และ Site](#การสร้าง-application-pool-และ-site)
- **06** · [การกำหนดกฎใน web.config](#การกำหนดกฎใน-web-config)
- **07** · [การส่งข้อมูล client ไปยัง upstream](#การส่งข้อมูล-client-ไปยัง-upstream)
- **08** · [การกำหนด HTTPS และ Binding](#การกำหนด-https-และ-binding)
- **09** · [การตรวจสอบและวิเคราะห์ปัญหา](#การตรวจสอบและวิเคราะห์ปัญหา)
- **10** · [การเตรียม Spring Boot ให้ทำงานกับ IIS](#การเตรียม-spring-boot-ให้ทำงานกับ-iis)
- **11** · [Tomcat และการพัฒนาแบบแยกส่วน](#tomcat-และการพัฒนาแบบแยกส่วน)
- **12** · [การเตรียม React ให้ทำงานกับ IIS](#การเตรียม-react-ให้ทำงานกับ-iis)
- **13** · [Full Stack Architecture](#full-stack-architecture)
- **—** · [อภิธานศัพท์](#อภิธานศัพท์)
- **—** · [แหล่งอ้างอิง](#แหล่งอ้างอิง)
- **◆** · [Knowledge Graph ของเอกสารทั้งฉบับ](#knowledge-graph-ของเอกสารทั้งฉบับ)

---

## ภาพรวมและขอบเขต

กลไกการทำงานภายในของ IIS หน่วยการจัดการ การติดตั้ง module การกำหนดกฎการเขียน URL และแนวทางวิเคราะห์ปัญหา สำหรับการนำเว็บแอปพลิเคชันขึ้นให้บริการบน Windows Server

- **ขอบเขต** — ชั้น IIS เป็นหลัก
- **เนื้อหา** — 13 ส่วน · แผนภาพ 16 รูป
- **อ้างอิง** — 36 รายการ · เปิดอ่านเต็มทุกหน้า

### เอกสารนี้ทำไว้เพื่ออะไร

ระบบหนึ่งระบบประกอบด้วยซอฟต์แวร์หลายตัวที่ทำงานคนละหน้าที่ คนที่เพิ่งเข้ามาร่วมทีมมักเห็นแต่ละตัวแยกกัน แต่ยังไม่เห็นว่ามันต่อกันตรงไหน เอกสารนี้จึงเขียนขึ้นเพื่อตอบสามคำถาม

| คำถาม | ตอบอยู่ที่ |
| --- | --- |
| **แต่ละตัวคืออะไร** — 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**

> **หมายเหตุ — ทำไมต้องเป็น IIS**
>
> ไม่ใช่เพราะบังคับ แต่เพราะ **ระบบรันอยู่บน 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

`ส่วนที่ 01`


*ความเข้าใจในส่วนนี้เป็นพื้นฐานของการวิเคราะห์ปัญหาในส่วนที่ 09*

IIS มักถูกเข้าใจว่าเป็นโปรแกรมเดียว แต่ในทางเทคนิคประกอบด้วยองค์ประกอบสามส่วนที่อยู่คนละระดับของระบบปฏิบัติการ ส่วนหนึ่งทำงานในเคอร์เนลของ Windows ส่วนหนึ่งทำงานเป็น Windows Service และอีกส่วนเป็น process ที่ถูกสร้างและยุติตามปริมาณการใช้งาน

> *แผนภาพ* — แผนภาพองค์ประกอบของ IIS แบ่งเป็น kernel mode ที่มี HTTP.sys พร้อม request queue และ response cache กับ user mode ที่มี svchost.exe ซึ่งบรรจุ W3SVC และ WAS อ่าน applicationHost.config แล้วสร้าง worker process

> **รูปที่ 1 — ตำแหน่งขององค์ประกอบแต่ละส่วน** · request เข้าสู่ระบบที่ HTTP.sys ซึ่งเป็น device driver ระดับเคอร์เนล ไม่ใช่โปรแกรมที่เปิดใช้งานได้โดยตรง ส่วน W3SVC และ WAS ทำงานภายใต้บัญชี `LocalSystem` ใน `svchost.exe` เดียวกัน และ `w3wp.exe` คือ process ที่ประมวลผล request จริง หนึ่ง process ต่อหนึ่ง Application Pool

### ลำดับการประมวลผล 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

---

## หน่วยการจัดการทั้งสี่

`ส่วนที่ 02`


*Site, Application และ Virtual Directory เป็นแกนของ URL ส่วน Application Pool เป็นแกนของ process ทั้งสองแกนเป็นอิสระต่อกัน*

ความเข้าใจที่คลาดเคลื่อนที่พบบ่อยที่สุดคือการมองว่า Application Pool อยู่ภายใต้ Site ในความเป็นจริง Site กำหนดว่า URL ใดแมปไปยังไฟล์ใด ขณะที่ Pool กำหนดว่าโค้ดนั้นทำงานใน process ใด และ Application จาก Site ที่ต่างกันสามารถใช้ Pool ร่วมกันได้

> *แผนภาพ* — แผนภาพแสดงว่า Site Application และ Virtual Directory เป็นแกนของ URL ส่วน Application Pool เป็นแกนของ process โดย Application ผูกกับ Pool ด้วยเส้นประ

> **รูปที่ 2 — ความสัมพันธ์ระหว่างสองแกน** · เส้นประแสดงการผูก Application เข้ากับ Pool จะสังเกตได้ว่า Virtual Directory ไม่มีเส้นเชื่อมของตนเอง เนื่องจากทำงานใน process ของ Application ที่ครอบอยู่ ข้อแตกต่างนี้เป็นเกณฑ์ในการตัดสินใจว่าควรสร้างหน่วยใด

### 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 จะถูกนำไปด้วยโดยอัตโนมัติ

**ลำดับล่างมีผลเหนือกว่า · configuration hierarchy**

```
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

`ส่วนที่ 03`


*ส่งไฟล์จากดิสก์ และส่งต่อ request ไปยัง upstream*

เมื่อ request เข้าสู่ `w3wp.exe` ตามลำดับในส่วนที่ 01 แล้ว IIS จะตัดสินใจอย่างใดอย่างหนึ่งในสองทางนี้เสมอ ทางแรกคือ **อ่านไฟล์จากดิสก์แล้วส่งกลับไปโดยตรง** ซึ่งใช้กับไฟล์ HTML, JavaScript, CSS และไฟล์ภาพ ทางที่สองคือ **ส่ง request ต่อไปยังเซิร์ฟเวอร์อื่นแล้วนำผลลัพธ์กลับมาให้ผู้ใช้** ซึ่งคือการทำ reverse proxy

ตัวที่กำหนดว่าจะเลือกทางใด คือกฎการเขียน URL ที่จะกล่าวถึงในส่วนที่ 06 ผลลัพธ์ที่ได้คือ **ทุกองค์ประกอบอยู่ภายใต้โดเมนเดียวกัน** ในสายตาของเบราว์เซอร์ จึงไม่จำเป็นต้องกำหนดค่า CORS และ cookie ของการเข้าสู่ระบบทำงานได้ตามปกติ

> *แผนภาพ* — แผนภาพสองหน้าที่ของ IIS request แบบ GET หน้าเว็บจะถูกตอบด้วยไฟล์จากดิสก์ด้วยสถานะ 200 ส่วน request ที่ขึ้นต้นด้วย /api จะถูกเขียน URL ใหม่แล้วส่งต่อไปยัง 127.0.0.1 port 8080 แล้วได้ JSON กลับมา

> **รูปที่ 3 — สองหน้าที่ที่แยกจากกันตั้งแต่ชั้น IIS** · เส้นทางด้านบนจบที่ดิสก์ของเซิร์ฟเวอร์ ไม่ผ่าน upstream เลย การแก้ไขไฟล์ static จึงทำได้โดยไม่กระทบ upstream ส่วนเส้นทางด้านล่างเป็นช่องทางเดียวที่เข้าถึง upstream ได้ เนื่องจากเซิร์ฟเวอร์นั้นผูกไว้ที่ `127.0.0.1`

### เหตุผลของการผูก upstream ไว้ที่ loopback

การกำหนดให้ upstream รับฟังเฉพาะ `127.0.0.1` แทน `0.0.0.0` ทำให้ **ไม่มีผู้ใดเรียกเข้าไปได้โดยตรงจากเครือข่าย** ทุก request ต้องผ่าน IIS ซึ่งเป็นจุดเดียวที่บังคับใช้ TLS ตรวจสอบสิทธิ์ระดับเครือข่าย และบันทึก log ข้อกำหนดนี้เป็นเงื่อนไขจำเป็นของแนวทางในส่วนที่ 07 ด้วย

> **หมายเหตุ — เหตุผลของกฎ “ไม่ตรงไฟล์ ให้ส่ง index.html”**
>
> เว็บแอปพลิเคชันที่จัดการ client-side routing หรือที่เรียกว่า SPA ไม่ได้มีไฟล์ `/orders/1024.html` อยู่บนดิสก์จริง หากผู้ใช้สั่ง refresh ที่หน้าดังกล่าว IIS จะไม่พบไฟล์และตอบกลับด้วยสถานะ **404** กฎข้อที่สองจึงกำหนดว่า หาก path ที่ร้องขอไม่ใช่ไฟล์และไม่ใช่โฟลเดอร์จริง ให้ส่ง `index.html` กลับไปแทน เพื่อให้โค้ดฝั่ง client ดำเนินการต่อ

---

## การติดตั้งองค์ประกอบที่จำเป็น

`ส่วนที่ 04`


*IIS มาตรฐานไม่รองรับการทำ reverse proxy ต้องติดตั้ง module เพิ่มสองรายการ และลำดับการติดตั้งมีผลต่อการทำงาน*

> *แผนภาพ* — แผนภาพลำดับการติดตั้งสี่ขั้น จาก Web Server role ไปยัง URL Rewrite ไปยัง ARR แล้วจบที่การเปิด Enable Proxy โดย ARR มี dependency กับ URL Rewrite

> **รูปที่ 5 — ลำดับการติดตั้งสี่ขั้น** · สามขั้นแรกเป็นการติดตั้งซอฟต์แวร์ ส่วนขั้นสุดท้ายเป็นการเปิดสวิตช์ที่มีอยู่แล้ว เส้นประด้านล่างคือความสัมพันธ์เชิงพึ่งพาที่ทำให้สลับลำดับไม่ได้

> **หมายเหตุ — ข้อกำหนดก่อนเริ่ม**
>
> คำสั่งทั้งหมดในส่วนนี้ต้องดำเนินการผ่าน PowerShell ที่เปิดด้วยสิทธิ์ **Run as administrator** เนื่องจากเอกสารของ `Install-WindowsFeature` ระบุว่า cmdlet นี้ต้องการสิทธิ์ระดับยกระดับ หากเซิร์ฟเวอร์เป็นเครื่องเสมือน ควรจัดทำ snapshot ไว้ก่อนเพื่อให้สามารถย้อนกลับได้

#### ติดตั้ง Web Server (IIS)

**PowerShell (Admin) · ขั้นที่ 1**

```powershell
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
```

เอกสารของ cmdlet ระบุว่า **เครื่องมือจัดการจะไม่ถูกติดตั้งโดยค่าเริ่มต้น เมื่อติดตั้งผ่าน `Install-WindowsFeature` ต้องเพิ่มพารามิเตอร์นี้จึงจะได้มาด้วย** ซึ่งต่างจากการติดตั้งผ่าน Add Roles and Features Wizard ที่ติดตั้งให้อยู่แล้ว

หากต้องการให้เครื่องเริ่มระบบใหม่อัตโนมัติเมื่อการติดตั้งกำหนดให้ต้องทำ สามารถเพิ่มพารามิเตอร์ `-Restart` ตามที่เอกสารระบุไว้

> **ผลลัพธ์ที่ควรได้รับ** ตรวจสอบสถานะด้วย `Get-WindowsFeature -Name Web-Server` ซึ่งเป็น cmdlet ที่เอกสารระบุไว้ในรายการที่เกี่ยวข้อง

#### ตรวจสอบสถานะของบริการ

**PowerShell · ขั้นที่ 2**

```powershell
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`

> **ผลลัพธ์ที่ควรได้รับ** เปิด IIS Manager และเลือก node ของเซิร์ฟเวอร์ จะปรากฏไอคอน *URL Rewrite* ใน Features View

#### ติดตั้ง 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 ภายในเครื่องแทนการส่งต่อออกไปภายนอก

> **หมายเหตุ — การตรวจสอบ module ที่ติดตั้งแล้ว**
>
> ใช้ `appcmd` เพื่อแสดงรายการ module ที่ลงทะเบียนกับ IIS ซึ่งเป็นวิธีที่ตรงที่สุดในการยืนยันว่าการติดตั้งสมบูรณ์
>
> **cmd (Admin) · ตรวจสอบ**
>
> ```
> %windir%\system32\inetsrv\appcmd.exe list modules
> ```

---

## การสร้าง Application Pool และ Site

`ส่วนที่ 05`


*มีค่าหนึ่งที่ต้องปรับจากค่าเริ่มต้นเสมอ*

> *แผนภาพ* — แผนภาพความสัมพันธ์ของสามสิ่งที่ต้องสร้าง คือโฟลเดอร์ Application Pool และ Site โดย Site อ้างถึงอีกสองอย่างผ่านพารามิเตอร์ และผลลัพธ์คือ worker process ที่รันภายใต้ virtual account

> **รูปที่ 11 — ความสัมพันธ์ของสามสิ่งที่ต้องสร้าง** · Site เป็นตัวกลางที่อ้างถึงทั้งโฟลเดอร์และ Pool ลำดับการสร้างจึงต้องเป็นโฟลเดอร์และ Pool ก่อน แล้วจึงสร้าง Site ส่วนบัญชีเสมือนถูกสร้างขึ้นพร้อม Pool โดยอัตโนมัติ

#### เตรียมโฟลเดอร์และวางไฟล์

**PowerShell (Admin) · ขั้นที่ 1**

```powershell
New-Item -ItemType Directory "D:\apps\corp-web" -Force

# คัดลอกไฟล์ static ทั้งชุดมาวางที่ตำแหน่งนี้
# ต้องมี index.html อยู่ที่ราก คือ D:\apps\corp-web\index.html
```

#### สร้าง Application Pool

**PowerShell (Admin) · ขั้นที่ 2**

```powershell
Import-Module WebAdministration

New-WebAppPool -Name "CorpWebPool"

# กำหนดเป็น No Managed Code — ค่าว่างหมายถึงไม่โหลด .NET runtime
Set-ItemProperty IIS:\AppPools\CorpWebPool -Name managedRuntimeVersion -Value ""
```

> **หมายเหตุ — เหตุผลของการกำหนด No Managed Code**
>
> ค่าเริ่มต้นของ pool ที่สร้างใหม่คือการโหลด .NET CLR เข้าสู่ process แต่ตามส่วนที่ 03 `w3wp.exe` ในสถาปัตยกรรมนี้ทำหน้าที่เพียงสองประการ คือส่งไฟล์ static และส่ง request ต่อไปยัง upstream โดยไม่มีโค้ด managed ที่ต้องประมวลผลภายใน process เลย การกำหนดเป็นค่าว่างจึงเป็นการตัดองค์ประกอบที่ไม่ได้ใช้งานออก ในหน้าจอ IIS Manager ค่านี้คือรายการ **.NET CLR version → No Managed Code**

#### สร้าง Site และผูกกับ Application Pool

**PowerShell (Admin) · ขั้นที่ 3**

```powershell
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

**cmd (Admin) · ให้สิทธิ์อ่าน**

```
icacls "D:\apps\corp-web" /grant "IIS AppPool\CorpWebPool":(OI)(CI)RX
```

หากโฟลเดอร์อยู่ภายใต้ path ที่สืบทอดสิทธิ์มาอย่างครบถ้วนอยู่แล้ว ขั้นตอนนี้อาจไม่จำเป็น ให้ตรวจสอบด้วยการเรียกหน้าเว็บก่อน หากได้รับสถานะ `401` หรือ `403` จึงค่อยกำหนดสิทธิ์เพิ่ม

---

## การกำหนดกฎใน web.config

`ส่วนที่ 06`


*ลำดับของกฎมีผลต่อการทำงานโดยตรง การสลับลำดับทำให้ระบบทำงานผิดพลาด*

จัดวางไฟล์นี้ไว้ที่ `D:\apps\corp-web\web.config` ซึ่งเป็นรากของ site ตำแหน่งเดียวกับ `index.html`

**D:\apps\corp-web\web.config · ทั้งไฟล์**

```xml
<?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>
```

> *แผนภาพ* — แผนภาพการตัดสินใจของกฎ rewrite สองข้อ กฎแรกตรวจว่า URL ขึ้นต้นด้วย api หรือไม่ ถ้าตรงจะ rewrite ไปยัง upstream ถ้าไม่ตรงจะเข้ากฎที่สองซึ่งตรวจว่าเป็นไฟล์จริงหรือไม่ ถ้าใช่จะส่งไฟล์นั้น ถ้าไม่ใช่จะส่ง index.html

> **รูปที่ 6 — เส้นทางการตัดสินใจของกฎสองข้อ** · หากสลับลำดับ กฎที่ 2 จะดักจับ `/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** ซึ่งเป็นปัญหาที่ตรวจหาสาเหตุได้ยาก หลักการคือกฎที่มีเงื่อนไขเฉพาะเจาะจงกว่าต้องอยู่ก่อนกฎที่มีเงื่อนไขกว้างกว่าเสมอ

> **หมายเหตุ — path ภายนอกไม่จำเป็นต้องตรงกับ path ภายใน**
>
> จะสังเกตได้ว่าการเรียกจากภายนอกใช้ `/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`** ตัวอย่างด้านล่างคัดมาจากเอกสารทางการโดยตรง

**web.config · ตัวอย่างจากเอกสาร Microsoft**

```xml
<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

`ส่วนที่ 07`


*เมื่อมี proxy คั่นกลาง upstream จะได้รับ IP ของ IIS แทน IP ของผู้ใช้*

> *แผนภาพ* — แผนภาพแสดงว่าการเชื่อมต่อถูกแบ่งเป็นสองช่วง ช่วงแรกจากผู้ใช้ถึง IIS มี remote address เป็น IP จริง ช่วงที่สองจาก IIS ถึง upstream มี remote address เป็น 127.0.0.1 ทำให้ IP เดิมต้องเดินทางผ่าน header

> **รูปที่ 7 — จุดที่หมายเลข IP เดิมหายไป** · ระหว่างทางมีการเชื่อมต่อสองช่วงที่แยกจากกัน ช่วงที่สองเกิดจาก IIS เอง upstream จึงอ่าน IP จริงจากชั้นเครือข่ายไม่ได้ ข้อมูลนั้นเดินทางต่อได้ในรูป header เท่านั้น

ประเด็นนี้เป็นผลสืบเนื่องที่เกิดขึ้นกับสถาปัตยกรรม 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 นี้มาด้วย** และกำหนดลำดับการดำเนินการไว้สามขั้นตามนี้

1. ติดตั้ง role service ชื่อ **Windows Authentication** ผ่าน Server Manager ที่ *Web Server (IIS) → Web Server → Security → Windows Authentication*
2. **ปิด** Anonymous authentication ของ site หรือ application นั้น
3. **เปิด** Windows authentication ของ site หรือ application นั้น

หลังติดตั้ง role service แล้ว IIS จะเขียนค่า `<windowsAuthentication enabled="false" />` ลงใน ApplicationHost.config กล่าวคือ **ค่าเริ่มต้นของแอตทริบิวต์ `enabled` คือ false** จึงต้องเปิดเองเสมอ

**cmd (Admin) · ขั้นที่ 2 และ 3**

```
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

`ส่วนที่ 08`


*TLS สิ้นสุดที่ชั้น IIS ส่วนการสื่อสารภายในเป็น HTTP ธรรมดาบน loopback*

> *แผนภาพ* — แผนภาพขอบเขตการเข้ารหัส ช่วงจากเบราว์เซอร์ถึง IIS เข้ารหัสด้วย TLS ส่วนช่วงจาก IIS ไปยัง upstream เป็น HTTP ธรรมดาบน loopback ภายในเครื่องเดียวกัน

> **รูปที่ 8 — ขอบเขตของการเข้ารหัส** · เส้นหนาคือช่วงที่เข้ารหัสด้วย TLS ซึ่งครอบคลุมทุกช่วงที่ข้อมูลเดินทางบนเครือข่าย ส่วนเส้นบางอยู่ภายในเครื่องเดียวกันทั้งหมด

ในสถาปัตยกรรมนี้ IIS เป็นจุดเดียวที่รับ TLS ขณะที่ช่วงระหว่าง IIS กับ upstream สื่อสารผ่าน `127.0.0.1` ซึ่งไม่ออกจากเครื่อง จึงไม่จำเป็นต้องทำ TLS ซ้ำอีกชั้น

**PowerShell (Admin) · ขั้นที่ 1 — สร้าง binding**

```powershell
# แสดงใบรับรองที่ติดตั้งอยู่ในเครื่อง และบันทึก 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 โดยตรง

**PowerShell (Admin) · ขั้นที่ 2 — ผูกใบรับรอง**

```powershell
# "my" คือชื่อ store ซึ่งตรงกับ Cert:\LocalMachine\My
(Get-WebBinding -Name "corp-web" -Port 443 -Protocol "https").AddSslCertificate(
  "a909502dd82ae41433e6f83886b00d4277a32a7b", "my")
```

แทนที่สตริง Thumbprint ด้วยค่าจริงของใบรับรองที่องค์กรใช้ ค่าที่แสดงไว้เป็นตัวอย่างจากเอกสารของ Microsoft

### การบังคับใช้ HTTPS

เพิ่มกฎต่อไปนี้เป็น**กฎลำดับแรกสุด** ภายใน `<rules>` ของไฟล์ `web.config` โดยอยู่ก่อนกฎทั้งสองข้อในส่วนที่ 06

**D:\apps\corp-web\web.config · แทรกเป็นกฎลำดับแรก**

```xml
<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 ที่ต้องการปกปิดโครงสร้างภายในจากผู้ใช้

---

## การตรวจสอบและวิเคราะห์ปัญหา

`ส่วนที่ 09`


*ระบุ log ที่ถูกต้องก่อน แล้วจึงวิเคราะห์สาเหตุ*

ตามรูปที่ 1 request เดินทางผ่านหลายชั้น และ **แต่ละชั้นบันทึกข้อมูลลงคนละไฟล์** การเปิด log ที่ไม่ตรงกับชั้นที่เกิดปัญหา เป็นสาเหตุที่ทำให้เข้าใจว่าระบบไม่ได้บันทึกข้อมูลใดไว้

> *แผนภาพ* — แผนภาพแสดงว่า request ที่ถูกปฏิเสธที่ HTTP.sys จะถูกบันทึกใน httperr ส่วนที่ถึง w3wp จะอยู่ใน IIS log และที่ส่งต่อออกไปจะอยู่ใน log ของ upstream

> **รูปที่ 4 — ชั้นที่ request สิ้นสุด และ log ที่ควรตรวจสอบ** · หากผู้ใช้แจ้งว่าเข้าใช้งานไม่ได้ แต่ log ของ IIS ไม่มีข้อมูล แสดงว่า request ไม่เคยเดินทางถึง `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) |

> **หมายเหตุ — วิธีจำแนกว่าปัญหาอยู่ที่ชั้น IIS หรืออยู่นอกขอบเขต**
>
> เรียกตรงไปยัง 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 ที่ต้องการติดตาม

**applicationHost.config · ผลลัพธ์ที่ IIS Manager เขียนให้**

```xml
<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

`ส่วนที่ 10`


*งานฝั่งแอปพลิเคชันที่ต้องสอดคล้องกับการตั้งค่า 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 กำหนดขั้นตอนการแปลงไว้ดังนี้

> *แผนภาพ* — แผนภาพเปรียบเทียบ packaging สองแบบ แบบ jar มี embedded tomcat อยู่ข้างในและรันเองได้ แบบ war ประกาศ tomcat เป็น provided แล้ววางใน webapps ของ Tomcat ที่ติดตั้งแยก พร้อมแสดงว่าชื่อไฟล์กำหนด context path

> **รูปที่ 9 — สองรูปแบบการส่งมอบ และที่มาของ context path** · ความต่างอยู่ที่ Tomcat ถูกบรรจุลงในไฟล์หรือไม่ ซึ่งควบคุมด้วย scope `provided` · ส่วนแถวล่างคือโซ่ของค่าที่ต้องตรงกันสามจุด ตั้งแต่ชื่อไฟล์จนถึงกฎใน `web.config`

#### ให้คลาสหลักสืบทอด SpringBootServletInitializer

และ override เมธอด `configure`

**MyApplication.java · ตัวอย่างจากเอกสาร Spring Boot**

```
@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

**pom.xml · maven**

```
<packaging>war</packaging>
```

#### ประกาศ servlet container ที่ฝังมาให้เป็น provided

**pom.xml · maven**

```
<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

> **หมายเหตุ — ความเชื่อมโยงกับกฎ rewrite**
>
> หากเปลี่ยนชื่อไฟล์ WAR ต้องแก้ปลายทางในกฎ `api-to-backend` ของส่วนที่ 06 ให้ตรงกัน เช่นเปลี่ยนเป็น `ROOT.war` ปลายทางจะกลายเป็น `http://127.0.0.1:8080/{R:1}` โดยไม่มีส่วน `/corp-api` นี่คือข้อดีของการแยกสองค่านี้ออกจากกัน — **แก้ที่ไฟล์เดียวโดยไม่ต้องแตะโค้ด**

> **ข้อควรระวัง — อย่ากำหนด Context ใน server.xml**
>
> เอกสารของ Tomcat ระบุว่า **ไม่แนะนำให้วางองค์ประกอบ `<Context>` ไว้ใน `server.xml` โดยตรง** เพราะทำให้การแก้ไขค่ามีผลกระทบมากขึ้น เนื่องจากไฟล์ `conf/server.xml` ไม่สามารถโหลดใหม่ได้โดยไม่รีสตาร์ท Tomcat อีกทั้งค่า Context เริ่มต้นจะเขียนทับค่าที่กำหนดไว้ตรงนั้น
>
> เอกสารยังระบุว่าแอตทริบิวต์ `path` **ต้องใช้เฉพาะเมื่อกำหนด Context แบบคงที่ใน server.xml เท่านั้น** ในกรณีอื่นค่า path จะถูกอนุมานจากชื่อไฟล์ให้เอง

### จำกัดการรับฟังของ Tomcat

ส่วนที่ 03 และ 07 ตั้งอยู่บนเงื่อนไขว่า upstream เข้าถึงได้จาก IIS เท่านั้น เอกสารของ HTTP Connector ระบุว่าแอตทริบิวต์ `address` ใช้กำหนดว่าจะรับฟังที่ที่อยู่ใด และ **โดยค่าเริ่มต้น connector จะรับฟังทุกที่อยู่ในเครื่อง** การจำกัดขอบเขตจึงต้องระบุแอตทริบิวต์นี้เอง

**conf/server.xml · Tomcat**

```
<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 |

**application.properties · Spring Boot**

```
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 และการพัฒนาแบบแยกส่วน

`ส่วนที่ 11`


*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 รุ่นนี้ไม่ได้

> *แผนภาพ* — แผนภาพโครงสร้างไดเรกทอรีของ Tomcat มี bin conf logs และ webapps โดยไฟล์ war ที่วางใน webapps จะกลายเป็น context path ที่ IIS ส่ง request ไปหา

> **รูปที่ 12 — โครงสร้างของ Tomcat และตำแหน่งที่ไฟล์ WAR ไปอยู่** · การวางไฟล์ลงใน `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 จึงจะรันสคริปต์นี้ได้**

**cmd (Admin) · รูปแบบจากเอกสาร Tomcat**

```
service.bat install

:: หรือระบุชื่อ service เองได้
service.bat install MyService
```

เอกสารระบุว่า `Tomcat11w` เป็นโปรแกรมแบบกราฟิกสำหรับ เฝ้าดูและกำหนดค่า Tomcat service · ส่วนไฟล์ `catalina.out` จะรับทุกอย่างที่ถูกเขียนลง `System.out` และ `System.err` ซึ่งรวมถึง exception ที่ไม่ถูกดักจับ จึงเป็นไฟล์แรกที่ควรเปิดดูเมื่อแอปไม่ทำงาน ตามตารางในส่วนที่ 09

> *แผนภาพ* — แผนภาพเปรียบเทียบสภาพแวดล้อมสองแบบ ตอนพัฒนาเบราว์เซอร์เรียก Vite dev server ที่ port 5173 ซึ่งส่งต่อ slash api ไปยัง Spring Boot ที่ port 8080 ส่วนตอนใช้งานจริงเบราว์เซอร์เรียก IIS ที่ port 443 ซึ่งส่งไฟล์ static เองและส่งต่อ slash api ไปยัง Tomcat

> **รูปที่ 13 — สองสภาพแวดล้อมที่มีรูปร่างเดียวกัน** · ตำแหน่งของตัวส่งต่อเปลี่ยนจาก Vite dev server เป็น IIS และปลายทางเปลี่ยนจาก Tomcat ที่ฝังมากับ Spring Boot เป็น Tomcat ที่ติดตั้งแยก แต่โค้ดฝั่ง frontend ยังเรียก `/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

`ส่วนที่ 12`


*ผลลัพธ์ของการ build และค่าที่ต้องตรงกับการตั้งค่า IIS*

ส่วนนี้จำกัดเฉพาะจุดที่ frontend มาบรรจบกับ IIS คือ **ไฟล์ที่ได้จากการ build วางอย่างไร** และ **ค่าใดต้องตรงกับกฎที่ตั้งไว้ในส่วนที่ 06** มิได้ครอบคลุมการเขียนคอมโพเนนต์หรือการออกแบบหน้าจอ

> *แผนภาพ* — แผนภาพเส้นทางของไฟล์จากซอร์สโค้ดผ่าน vite build ได้โฟลเดอร์ dist แล้วคัดลอกไปยังโฟลเดอร์ของ site บน IIS โดย web.config มาจากคนละแหล่งและต้องไม่ถูกลบ

> **รูปที่ 10 — เส้นทางของไฟล์และจุดที่ต้องระวัง** · ไฟล์ในโฟลเดอร์ปลายทางมาจากสองแหล่งที่ไม่เหมือนกัน ไฟล์ของ frontend ถูกสร้างใหม่ทุกครั้งที่ build ส่วน `web.config` เขียนขึ้นครั้งเดียวและต้องอยู่ต่อไป

### ผลลัพธ์ของการ build คือไฟล์ static ล้วน

เอกสารของ Vite ระบุว่าเมื่อสั่ง `npm run build` **ผลลัพธ์จะถูกวางไว้ที่โฟลเดอร์ `dist` โดยค่าเริ่มต้น และสามารถนำโฟลเดอร์นี้ไป deploy บนแพลตฟอร์มใดก็ได้** เนื้อหาภายในประกอบด้วยไฟล์ HTML หนึ่งไฟล์ กับไฟล์ JavaScript และ CSS ที่เอกสารเรียกว่า *hashed assets*

> **หมายเหตุ — เหตุผลที่ IIS ส่งไฟล์เหล่านี้ได้เอง**
>
> เพราะไม่มีไฟล์ใดต้องผ่านตัวแปลภาษาฝั่งเซิร์ฟเวอร์เลย งานของ 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

**สั่งตอน build · รูปแบบจากเอกสาร Vite**

```
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

`ส่วนที่ 13`


*ร้อยทุกส่วนที่ผ่านมาเข้าด้วยกัน — React, IIS, Tomcat และ Spring Boot ในเส้นทางเดียว*

ส่วนที่ 01 ถึง 12 อธิบายแต่ละชิ้นแยกกันว่าคืออะไรและตั้งค่าอย่างไร ส่วนนี้นำทั้งหมดมาประกอบเป็นภาพเดียว เพื่อตอบสามคำถามที่เหลือ คือ **สถาปัตยกรรมแบบนี้เรียกว่าอะไร · ทำไมถึงเลือกแบบนี้ · และค่าใดบ้างที่ต้องตรงกันตลอดเส้นทาง**

### สถาปัตยกรรมแบบนี้เรียกว่าอะไร

รูปแบบที่เราใช้มีชื่อเรียกอยู่แล้ว การรู้ชื่อมีประโยชน์เวลาค้นหาข้อมูลเพิ่มเติม หรือคุยกับคนนอกทีม เพราะคำเหล่านี้เป็นคำที่ใช้กันทั่วไปในวงการ

### ทำไมถึงเลือกแบบนี้

ทางเลือกอื่นมีอยู่ และไม่ได้แย่กว่าเสมอไป แต่ในเงื่อนไขของงานนี้ 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 เลย**

> *แผนภาพ* — แผนภาพลำดับเวลาสี่เลน คือ Browser IIS Tomcat และ Spring Boot เวลาไหลจากซ้ายไปขวา ช่วงที่หนึ่งคือ document request สามขั้นซึ่งจบที่ IIS ช่วงที่สองคือ XHR ห้าขั้นซึ่งลงไปถึง Spring Boot แล้วส่ง JSON กลับขึ้นมา

> **รูปที่ 14 — ทุกส่วนประกอบกันเป็นระบบเดียว** · เส้นแนวนอนด้านบนคือเส้นทางของ request ที่เรียก API ส่วนเส้นที่วกกลับลงมาคือการส่งไฟล์ static ซึ่งจบที่ IIS ไม่ได้ออกไปไกลกว่านั้น สังเกตว่า React อยู่ฝั่งเครื่องผู้ใช้ ไม่ได้อยู่บนเซิร์ฟเวอร์

> *แผนภาพ* — แผนภาพเปรียบเทียบ process ที่รันจริง ฝั่งเครื่องพัฒนามี process Node ที่รัน Vite และ process JVM ที่รัน Spring Boot ฝั่งเซิร์ฟเวอร์มี process w3wp ของ IIS และ process JVM ของ Tomcat โดยมีขั้นตอน build คั่นกลาง

> **รูปที่ 15 — สิ่งที่รันจริงบนเครื่อง เทียบสองสภาพแวดล้อม** · แผนภาพนี้ไม่ได้แสดงเส้นทางของ request แต่แสดงว่า **มีโปรแกรมอะไรทำงานอยู่บ้างและอยู่ที่ไหน** ซึ่งเป็นคนละคำถามกับรูปก่อนหน้า และเป็นสิ่งที่ต้องรู้เวลาเปิดเครื่องทำงานจริง

### ใครรับผิดชอบช่วงไหน

| ส่วนประกอบ | ทำงานที่ | หน้าที่ | ส่วนที่ |
| --- | --- | --- | --- |
| 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 Architectures** `[MICROSOFT]`<br>องค์ประกอบ HTTP.sys, W3SVC และ WAS, คุณประโยชน์สามประการของ HTTP.sys, บทบาทของ application pool และลำดับการประมวลผล request แปดขั้น<br><https://learn.microsoft.com/en-us/iis/get-started/introduction-to-iis/introduction-to-iis-architecture> |
| 02 | **Application Pool Identities** `[MICROSOFT]`<br>บัญชีเสมือนรูปแบบ `IIS AppPool\<ชื่อ pool>`, ค่าเริ่มต้น ApplicationPoolIdentity และตัวอย่างคำสั่ง `ICACLS`<br><https://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 Routing** `[MICROSOFT]`<br>ข้อความว่าความสามารถ reverse proxy ถูกปิดเป็นค่าเริ่มต้น และรูปแบบกฎที่ใช้ในส่วนที่ 06<br><https://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 Reference** `[MICROSOFT]`<br>นิยามของ `IsFile`, `IsDirectory`, `negate`, `logicalGrouping`, `stopProcessing`, back-reference `{R:N}`, ค่า `redirectType` และตัวแปร `HTTPS` ที่มีค่า `OFF`<br><https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference> |
| 05 | **Creating Rewrite Rules for the URL Rewrite Module** `[MICROSOFT]`<br>ความแตกต่างระหว่าง action แบบ Rewrite และ Redirect และตำแหน่งที่เก็บกฎ<br><https://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 SkyDrive** `[MICROSOFT]`<br>เหตุผลของการตั้ง `preserveHostHeader = true` และการบันทึกสถานะ 502 ของ ARR<br><https://learn.microsoft.com/en-us/iis/extensions/configuring-application-request-routing-arr/arr-as-generic-proxy-in-hotmail-and-skydrive> |
| 07 | **URL Rewrite** `[MICROSOFT]`<br>หน้าดาวน์โหลดทางการ ระบุเวอร์ชัน 2.1 รองรับ IIS 7 ถึง IIS 10<br><https://iis.net/downloads/microsoft/url-rewrite> |
| 08 | **Application Request Routing** `[MICROSOFT]`<br>หน้าดาวน์โหลดทางการ ระบุเวอร์ชัน 3.0 และข้อกำหนดว่าต้องติดตั้ง URL Rewrite ก่อน<br><https://iis.net/downloads/microsoft/application-request-routing> |

### เนื้อหาแบบ static status code และ log

| # | เรื่อง / แหล่ง |
| --- | --- |
| 09 | **Adding Static Content MIME Mappings <mimeMap>** `[MICROSOFT]`<br>ข้อความว่า IIS ไม่ส่งคืนชนิดไฟล์ที่ไม่ได้เพิ่มไว้ใน `<staticContent>` และรูปแบบการประกาศ<br><https://learn.microsoft.com/en-us/iis/configuration/system.webserver/staticcontent/mimemap> |
| 10 | **HTTP Status Code Overview (IIS)** `[MICROSOFT]`<br>นิยามของ `404.3` MIME type restriction, `400.10` Invalid X-Forwarded-For header, `502` และรหัสย่อยของ ARR<br><https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/health-diagnostic-performance/http-status-code> |
| 11 | **Configure Logging in IIS** `[MICROSOFT]`<br>ตำแหน่งเริ่มต้นของ log IIS คือ `%SystemDrive%\inetpub\logs\LogFiles`<br><https://learn.microsoft.com/en-us/iis/manage/provisioning-and-managing-iis/configure-logging-in-iis> |
| 12 | **Configuring HTTP Server API Error Logging** `[MICROSOFT]`<br>โฟลเดอร์เริ่มต้น `%SystemRoot%\System32\LogFiles` พร้อมโฟลเดอร์ย่อย `HTTPERR` และ registry ที่ควบคุมการบันทึก<br><https://learn.microsoft.com/en-us/windows/win32/http/configuring-http-server-api-error-logging> |
| 13 | **Troubleshoot failed requests using tracing in IIS** `[MICROSOFT]`<br>หลักการเก็บ trace ไว้ในบัฟเฟอร์ ตำแหน่ง `FailedReqLogFiles\W3SVC<id>` และโครงสร้าง configuration<br><https://learn.microsoft.com/en-us/previous-versions/troubleshoot/iis/troubleshoot-failed-requests-using-tracing-in-iis-7> |

### การยืนยันตัวตนและ cmdlet

| # | เรื่อง / แหล่ง |
| --- | --- |
| 14 | **Windows Authentication <windowsAuthentication>** `[MICROSOFT]`<br>ลำดับสามขั้นของการเปิดใช้งาน ค่าเริ่มต้น `enabled="false"` คำสั่ง appcmd และข้อกำหนดเรื่อง `/commit:apphost`<br><https://learn.microsoft.com/en-us/iis/configuration/system.webserver/security/authentication/windowsauthentication/> |
| 15 | **Install-WindowsFeature (ServerManager)** `[MICROSOFT]`<br>ข้อกำหนดด้านสิทธิ์ระดับยกระดับ และพารามิเตอร์ `-IncludeManagementTools`<br><https://learn.microsoft.com/en-us/powershell/module/servermanager/install-windowsfeature> |
| 16 | **New-WebAppPool, New-Website, New-WebBinding (WebAdministration)** `[MICROSOFT]`<br>syntax ของ cmdlet, ค่า `-SslFlags` ทั้งสี่ค่า และตัวอย่างการผูกใบรับรองด้วย `AddSslCertificate`<br><https://learn.microsoft.com/en-us/powershell/module/webadministration/> |

### Spring Boot และ Apache Tomcat · ส่วนที่ 10

| # | เรื่อง / แหล่ง |
| --- | --- |
| 17 | **Traditional Deployment** · Spring Boot `[SPRING]`<br>ขั้นตอนสร้างไฟล์ WAR ที่ deploy ได้ ทั้งการสืบทอด `SpringBootServletInitializer`, การเปลี่ยน packaging การประกาศ `spring-boot-starter-tomcat` เป็น `provided` และข้อจำกัดของ Spring WebFlux<br><https://docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html> |
| 18 | **Embedded Web Servers** · Spring Boot `[SPRING]`<br>property `server.forward-headers-strategy` ค่า NATIVE, FRAMEWORK, NONE และคำเตือนเรื่องการเชื่อถือ proxy ทุกตัว<br><https://docs.spring.io/spring-boot/how-to/webserver.html> |
| 19 | **The Context Container** · Apache Tomcat 11 `[APACHE]`<br>กฎการอนุมาน context path จากชื่อไฟล์ WAR, กรณีพิเศษ `ROOT`, การใช้ `#` และข้อไม่แนะนำให้กำหนด Context ใน `server.xml`<br><https://tomcat.apache.org/tomcat-11.0-doc/config/context.html> |
| 20 | **The HTTP Connector** · Apache Tomcat 11 `[APACHE]`<br>แอตทริบิวต์ `address` และข้อความว่าโดยค่าเริ่มต้น connector รับฟังทุกที่อยู่ในเครื่อง<br><https://tomcat.apache.org/tomcat-11.0-doc/config/http.html> |

### React และ Vite · ส่วนที่ 12

| # | เรื่อง / แหล่ง |
| --- | --- |
| 21 | **Building for Production** · Vite `[VITE]`<br>หัวข้อ Public Base Path การเขียน path ของ asset ใหม่ตามค่า `base` และการเรียกไฟล์ผลลัพธ์ว่า hashed assets<br><https://vite.dev/guide/build> |
| 22 | **Deploying a Static Site** · Vite `[VITE]`<br>ข้อความว่าผลลัพธ์ของการ build ถูกวางที่ `dist` โดยค่าเริ่มต้น และคำสั่ง `npm run preview` สำหรับทดสอบในเครื่อง<br><https://vite.dev/guide/static-deploy> |
| 23 | **Shared Options** · Vite `[VITE]`<br>นิยามของค่า `base` และรูปแบบค่าที่รับได้<br><https://vite.dev/config/shared-options> |
| 24 | **Server Options** · Vite `[VITE]`<br>ค่า `server.proxy` ซึ่งเอกสารระบุว่าใช้กำหนดกฎ proxy สำหรับ dev server<br><https://vite.dev/config/server-options> |
| 25 | **Single Page App (SPA)** · React Router `[REACT ROUTER]`<br>หัวข้อ Direct all URLs to index.html ข้อกำหนดให้โฮสต์ส่งทุก URL ไปยัง `index.html` และข้อความเรื่องอาการ 404 ที่เส้นทางซึ่งถูกต้อง<br><https://reactrouter.com/how-to/spa> |

### Tomcat และการพัฒนา · ส่วนที่ 11

| # | เรื่อง / แหล่ง |
| --- | --- |
| 26 | **Which Version Do I Want?** · Apache Tomcat `[APACHE]`<br>ตารางเวอร์ชัน Tomcat กับสเปกและเวอร์ชัน Java ที่รองรับ และข้อความว่า Tomcat 11.0.x implement Servlet 6.1 ตามที่ Jakarta EE 11 กำหนด<br><https://tomcat.apache.org/whichversion.html> |
| 27 | **Introduction** · Apache Tomcat 11 `[APACHE]`<br>นิยามของ Tomcat โครงสร้างไดเรกทอรี `/bin` `/conf` `/logs` `/webapps` และความต่างของ `CATALINA_HOME` กับ `CATALINA_BASE`<br><https://tomcat.apache.org/tomcat-11.0-doc/introduction.html> |
| 28 | **Deployer How To** · Apache Tomcat 11 `[APACHE]`<br>พฤติกรรมของ `autoDeploy` เมื่อมีไฟล์ .WAR ถูกวางลงใน appBase<br><https://tomcat.apache.org/tomcat-11.0-doc/deployer-howto.html> |
| 29 | **Windows Service How To** · Apache Tomcat 11 `[APACHE]`<br>สคริปต์ `service.bat` ข้อกำหนดเรื่องสิทธิ์ Administrator และโปรแกรม `Tomcat11w`<br><https://tomcat.apache.org/tomcat-11.0-doc/windows-service-howto.html> |
| 30 | **Logging** · Apache Tomcat 11 `[APACHE]`<br>บทบาทของไฟล์ `catalina.out` ที่รับทุกอย่างซึ่งเขียนลง `System.out` และ `System.err`<br><https://tomcat.apache.org/tomcat-11.0-doc/logging.html> |
| 31 | **System Requirements** · Spring Boot `[SPRING]`<br>เวอร์ชัน Java ที่ต้องใช้ และรายการ servlet container ที่รองรับพร้อมเลขเวอร์ชัน Servlet<br><https://docs.spring.io/spring-boot/system-requirements.html> |
| 32 | **Developer Tools** · Spring Boot `[SPRING]`<br>การรีสตาร์ทอัตโนมัติเมื่อไฟล์บน classpath เปลี่ยนแปลง<br><https://docs.spring.io/spring-boot/reference/using/devtools.html> |

### Knowledge Graph · หน้า ◆

| # | เรื่อง / แหล่ง |
| --- | --- |
| 33 | **Linked Data** · Design Issues `[W3C]`<br>กฎสี่ข้อของ Linked Data ตั้งแต่ *“Use URIs as names for things”* ถึง *“Include links to other URIs. so that they can discover more things.”* และ 5-star deployment scheme<br><https://www.w3.org/DesignIssues/LinkedData.html> |
| 34 | **RDF 1.1 Primer** · W3C `[W3C]`<br>นิยามของ triple ว่า subject กับ object คือ resource สองตัวที่ถูกเชื่อม ส่วน predicate คือลักษณะของความสัมพันธ์ · ข้อความว่า IRI เป็น global identifier · และกลไกที่ resource เดียวกันไปอยู่ทั้งฝั่ง subject และ object ทำให้พบความเชื่อมโยงระหว่าง triple ได้<br><https://www.w3.org/TR/rdf11-primer/> |
| 35 | **Graph database concepts** · Neo4j `[NEO4J]`<br>นิยามของ node, label, relationship, relationship type, property, traversal และ path ที่ใช้ในหน้า Knowledge Graph<br><https://neo4j.com/docs/getting-started/appendix/graphdb-concepts/> |
| 36 | **Weakly Connected Components** · Neo4j Graph Data Science `[NEO4J]`<br>อัลกอริทึมหาชุดของ node ที่เดินถึงกันได้ และข้อความว่าใช้ตัดสินได้ว่าเครือข่ายเชื่อมถึงกันทั้งหมดหรือไม่<br><https://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”*

**WAR กับ Tomcat ไม่มีเส้นตรงถึงกัน แต่ถึงกันผ่าน webapps · triple**

```
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 |

ทั้งหมด 54 IIS 24 Java 13 React 9 สถาปัตยกรรม 8

> *แผนภาพ* — Knowledge Graph ของเอกสาร ประกอบด้วย 54 node และ 65 เส้นความสัมพันธ์ แบ่งเป็นสี่หมวดคือ IIS, Java, React และสถาปัตยกรรม ตรวจแล้วเชื่อมถึงกันทั้งหมดเป็น component เดียว

ยังไม่ได้เลือก node ทั้งกราฟ · 54 node · 65 relationship · WCC = 1 component

คลิกที่กล่องใดก็ได้ในภาพ เพื่อดูว่าสิ่งนั้นต่อกับอะไรบ้าง ด้วย relationship ชื่ออะไร และอยู่ในส่วนที่เท่าไรของเอกสาร · ชี้ที่เส้นเพื่ออ่านชื่อ relationship บนภาพ

> **รูปที่ 16 — Knowledge Graph ของเอกสารทั้งฉบับ** · 54 node และ 65 relationship แบ่งด้วยสีตามหมวด ตำแหน่งของกล่องคำนวณด้วย force layout แบบ deterministic นอกเบราว์เซอร์ แล้วตรึงพิกัดไว้ จึงตรวจได้ว่าไม่มีกล่องทับกันและไม่มีเส้นพาดผ่านกล่องใด ชื่อ 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 จุด |

> **หมายเหตุ — ทำไมไม่เขียนชื่อ relationship ค้างไว้บนภาพ**
>
> ลองวางป้ายชื่อทั้ง 65 เส้นแล้วตรวจดู พบว่า **30 จาก 54 กรณีมีป้ายทับกันหรือทับกล่อง** ซึ่งขัดกับข้อกำหนดของเอกสารนี้ จึงเปลี่ยนเป็นคำนวณตำแหน่งที่ว่างของแต่ละเส้นไว้ล่วงหน้า แล้วแสดงทีละเส้นตอนชี้ ส่วนภาพรวมของความสัมพันธ์ทั้งหมดอ่านได้จากตาราง triple ด้านบน

---

*เอกสารนี้สร้างจากฉบับเว็บโดยอัตโนมัติ · ชื่อ site, path, หมายเลข port*
*และ path ปลายทางทั้งหมดเป็นค่าตัวอย่างที่กำหนดขึ้น มิใช่ค่าจากระบบจริง*
