ทำไมต้องมีเป้าหมายในการออกแบบ RESTful
หลักการ RESTful นำคุณสมบัติที่ทำให้เว็บไซต์เป็นเรื่องง่าย (สำหรับผู้ใช้แบบสุ่มเพื่อ "ท่อง") ในการออกแบบเว็บเซอร์วิส APIดังนั้นจึงเป็นเรื่องง่ายสำหรับโปรแกรมเมอร์ที่จะใช้งาน REST ไม่ดีเพราะ REST ดีเพราะดี และมันก็เป็นสิ่งที่ดีมากเพราะมันเป็นเรื่องง่าย
ความเรียบง่ายของธรรมดา HTTP (ไม่มีซองสบู่และเดียวมากเกินไป URI POST
บริการ) สิ่งที่บางคนอาจจะเรียก"ขาดคุณสมบัติ"เป็นจริงพลังที่ยิ่งใหญ่ของมัน HTTP ทันทีที่ขอให้คุณมีความสามารถในการกำหนดแอดเดรสและการไร้สัญชาติ : การตัดสินใจการออกแบบพื้นฐานสองรายการที่ทำให้ HTTP สามารถปรับขนาดได้จนถึงไซต์ขนาดใหญ่ในปัจจุบัน (และบริการเมกะ)
แต่ REST ไม่ใช่ Bulltet เงิน: บางครั้ง RPC-style ("Remote Procedure Call" - เช่น SOAP) อาจเหมาะสมและบางครั้งความต้องการอื่น ๆ ก็มีความสำคัญเหนือกว่าคุณธรรมของเว็บ นี่เป็นเรื่องปกติ สิ่งที่เราไม่ชอบความซับซ้อนจำเป็น บ่อยครั้งที่โปรแกรมเมอร์หรือ บริษัท นำบริการ RPC-style มาใช้กับงานที่ HTTP แบบเก่าธรรมดาสามารถจัดการได้ ผลที่ได้คือ HTTP จะถูกลดขนาดไปที่โปรโตคอลการขนส่งสำหรับส่วนของข้อมูล XML ขนาดใหญ่ที่อธิบายว่าเกิดอะไรขึ้น "จริง ๆ " (ไม่ใช่ URI หรือวิธี HTTP ให้เบาะแสเกี่ยวกับมัน) บริการที่ได้นั้นซับซ้อนเกินไปไม่สามารถทำการดีบักได้และจะไม่ทำงานจนกว่าลูกค้าของคุณจะมีการตั้งค่าที่แน่นอนตามที่นักพัฒนาตั้งใจไว้
ในทำนองเดียวกันโค้ด Java / C # ไม่สามารถเป็นวัตถุได้เพียงแค่ใช้ HTTP ไม่ได้ทำให้การออกแบบสงบ อาจมีใครตกอยู่ในความรีบเร่งในการคิดเกี่ยวกับบริการของพวกเขาในแง่ของการกระทำและวิธีการระยะไกลที่ควรเรียก ไม่น่าแปลกใจที่ส่วนใหญ่จะสิ้นสุดในบริการ RPC-Style (หรือ REST-RPC-hybrid) ขั้นตอนแรกคือการคิดต่างกัน การออกแบบ RESTful สามารถทำได้หลายวิธีวิธีหนึ่งคือคิดถึงแอปพลิเคชันของคุณในแง่ของทรัพยากรไม่ใช่การกระทำ:
💡แทนที่จะคิดในแง่ของการกระทำที่สามารถทำได้ ("ค้นหาสถานที่บนแผนที่") ...
... พยายามคิดในแง่ของผลลัพธ์ของการกระทำเหล่านั้น ("รายการสถานที่บนแผนที่ที่ตรงกับเกณฑ์การค้นหา")
ฉันจะไปตัวอย่างด้านล่าง (แง่มุมสำคัญอื่น ๆ ของ REST คือการใช้ HATEOAS - ฉันไม่ได้แปรงที่นี่ แต่ฉันพูดถึงมันอย่างรวดเร็วที่โพสต์อื่น )
ปัญหาของการออกแบบครั้งแรก
ลองมาดูการออกแบบที่เสนอ:
ACTION http://api.animals.com/v1/dogs/1/
ก่อนอื่นเราไม่ควรพิจารณาสร้างกริยา HTTP ใหม่ ( ACTION
) โดยทั่วไปการพูดนี้ไม่พึงประสงค์ด้วยเหตุผลหลายประการ:
- (1)ด้วย URI ของบริการเท่านั้นโปรแกรมเมอร์ "สุ่ม" จะรู้
ACTION
คำกริยาอย่างไร
- (2)ถ้าโปรแกรมเมอร์รู้ว่ามันมีอยู่เขาจะรู้ว่าความหมายของมันได้อย่างไร? คำกริยานั้นแปลว่าอะไร?
- (3)ควรมีคุณสมบัติใด (ความปลอดภัยความเฉื่อยชา) ที่คาดหวังว่าคำกริยานั้นมี?
- (4) จะเป็นอย่างไรถ้าโปรแกรมเมอร์มีไคลเอนต์แบบง่าย ๆ ที่จัดการกริยา HTTP มาตรฐานเท่านั้น
- (5) ...
ตอนนี้ลองพิจารณาใช้POST
(ฉันจะอธิบายว่าทำไมด้านล่างเพียงใช้คำของฉันตอนนี้):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
นี่อาจจะโอเค ... แต่ถ้า :
{"action":"bark"}
เป็นเอกสาร และ
/v1/dogs/1/
เป็น "ตัวประมวลผลเอกสาร" (เหมือนโรงงาน) URI "ตัวประมวลผลเอกสาร" เป็น URI ที่คุณเพียงแค่ "โยนสิ่งต่าง ๆ ที่" และ "ลืม" เกี่ยวกับพวกเขา - โปรเซสเซอร์อาจเปลี่ยนเส้นทางคุณไปยังทรัพยากรที่สร้างขึ้นใหม่หลังจาก "โยน" เช่น URI สำหรับการโพสต์ข้อความที่บริการนายหน้าข้อความซึ่งหลังจากการโพสต์จะนำคุณไปยัง URI ที่แสดงสถานะของการประมวลผลข้อความ
ฉันไม่รู้เกี่ยวกับระบบของคุณมากนัก แต่ฉันพนันได้เลยว่าไม่เป็นความจริงเลย:
{"action":"bark"}
ไม่ใช่เอกสารจริงๆแล้วมันเป็นวิธีการที่คุณพยายามจะแอบเข้าไปในบริการ และ
/v1/dogs/1/
URI หมายถึง "สุนัข" ทรัพยากร (อาจเป็นสุนัขที่มีid==1
) และไม่ได้ประมวลผลเอกสาร
ดังนั้นทั้งหมดที่เรารู้ในตอนนี้ก็คือการออกแบบด้านบนนั้นไม่สงบดังนั้น แต่นั่นคืออะไรกันแน่ อะไรที่เลวร้ายเกี่ยวกับมัน? โดยทั่วไปมันไม่ดีเพราะนั่นคือ URI ที่ซับซ้อนพร้อมความหมายที่ซับซ้อน คุณไม่สามารถอนุมานอะไรจากมัน โปรแกรมเมอร์จะรู้ได้อย่างไรว่าสุนัขมีการbark
กระทำที่สามารถแทรกซึมPOST
เข้าไปในที่ลับได้?
การออกแบบการโทร API ของคำถามของคุณ
ดังนั้นขอให้ตัดเพื่อการไล่ล่าและพยายามที่จะออกแบบเปลือกผู้เอ้โดยการคิดในแง่ของทรัพยากร ให้ฉันอ้างอิงหนังสือRestful Web Services :
การPOST
ร้องขอคือความพยายามในการสร้างทรัพยากรใหม่จากที่มีอยู่ ทรัพยากรที่มีอยู่อาจเป็นพาเรนต์ของแหล่งข้อมูลใหม่ในความหมายโครงสร้างข้อมูลวิธีที่รูทของต้นไม้เป็นพาเรนต์ของโหนดโหนดทั้งหมด หรือทรัพยากรที่มีอยู่อาจเป็นทรัพยากร"โรงงาน"พิเศษ
ซึ่งมีวัตถุประสงค์เพียงอย่างเดียวคือการสร้างทรัพยากรอื่น ๆ การเป็นตัวแทนที่ส่งไปพร้อมกับPOST
คำขอจะอธิบายถึงสถานะเริ่มต้นของทรัพยากรใหม่ เช่นเดียวกับ PUT POST
คำขอไม่จำเป็นต้องรวมการเป็นตัวแทนเลย
จากคำอธิบายข้างต้นเราจะเห็นว่าbark
สามารถจำลองเป็นsubresource ของ adog
(เนื่องจาก a bark
อยู่ในสุนัขนั่นคือเปลือกคือ "barked" โดยสุนัข)
จากเหตุผลนั้นเราได้:
- วิธีการคือ
POST
- ทรัพยากรคือแหล่งข้อมูล
/barks
ย่อยของ dog: /v1/dogs/1/barks
ซึ่งหมายถึงbark
"factory" URI นั้นมีลักษณะเฉพาะสำหรับสุนัขแต่ละตัว (เนื่องจากอยู่ใต้/v1/dogs/{id}
)
ตอนนี้แต่ละกรณีในรายการของคุณมีพฤติกรรมที่เฉพาะเจาะจง
1. เรือสำเภาเพียงส่งอีเมลไปที่dog.email
และไม่บันทึกอะไรเลย
ประการแรกการเห่า (ส่งอีเมล) เป็นงานที่ทำข้อมูลให้ตรงกันหรืองานที่ทำข้อมูลให้ตรงกัน ประการที่สองที่ไม่bark
คำขอต้องใช้เอกสารใด ๆ (e-mail, อาจจะ) หรือมันว่างเปล่า
1.1 bark ส่งอีเมลไปยังdog.email
และไม่บันทึกสิ่งใด (เป็นงานที่ทำข้อมูลให้ตรงกัน)
กรณีนี้ง่าย การเรียกใช้barks
ทรัพยากรของโรงงานจะให้เปลือก (ส่งอีเมล) ทันทีและจะได้รับการตอบกลับ (ถ้าตกลงหรือไม่):
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
ตามที่บันทึกไว้ (เปลี่ยนแปลง) ไม่มีอะไร200 OK
เพียงพอ มันแสดงให้เห็นว่าทุกอย่างเป็นไปตามที่คาดไว้
1.2 เปลือกส่งอีเมลdog.email
และบันทึกสิ่งใด (เป็นงานอะซิงโครนัส)
ในกรณีนี้ลูกค้าจะต้องมีวิธีการติดตามbark
งาน bark
งานแล้วควรจะเป็นทรัพยากรที่มีเป็นของตัวเอง URI .:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
ด้วยวิธีนี้แต่ละคนbark
สามารถติดตามได้ จากนั้นไคลเอ็นต์สามารถออก a GET
ถึงbark
URI เพื่อให้ทราบสถานะปัจจุบัน อาจใช้DELETE
เพื่อยกเลิก
2. เปลือกไม้ส่งอีเมลไปที่dog.email
แล้วเพิ่มทีละdog.barkCount
1
สิ่งนี้อาจมีเล่ห์เหลี่ยมถ้าคุณต้องการให้ลูกค้าทราบว่าdog
ทรัพยากรได้รับการเปลี่ยนแปลง:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
ในกรณีนี้ความตั้งใจหัวคือการปล่อยให้ลูกค้ารู้ว่าเขาควรจะดูที่location
dog
จากHTTP RFC เกี่ยวกับ303
:
วิธีการนี้มีอยู่เป็นหลักเพื่อให้เอาต์พุตของ
POST
สคริปต์ที่เรียกทำงานเพื่อเปลี่ยนเส้นทางตัวแทนผู้ใช้ไปยังทรัพยากรที่เลือก
หากภารกิจนั้นไม่ตรงกันbark
จำเป็นต้องมีแหล่งข้อมูลย่อยเช่นเดียวกับ1.2
สถานการณ์และ303
ควรส่งคืนที่GET .../barks/Y
เมื่องานเสร็จสมบูรณ์
3. เปลือกไม้สร้างbark
เร็กคอร์ดใหม่ " " พร้อมbark.timestamp
การบันทึกเมื่อเปลือกเกิดขึ้น นอกจากนี้ยังเพิ่มขึ้นทีละdog.barkCount
1
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
ในที่นี่bark
มีการสร้างเนื่องจากคำขอดังนั้นสถานะ201 Created
จะถูกนำไปใช้
หากการสร้างแบบอะซิงโครนัส202 Accepted
จำเป็นต้องใช้a ( ตามที่ HTTP RFC บอกว่า ) แทน
การประทับเวลาที่บันทึกไว้เป็นส่วนหนึ่งของbark
ทรัพยากรและสามารถเรียกดูกับGET
มัน สุนัขที่ได้รับการอัพเดตสามารถ "บันทึก" ในสิ่งนั้นได้GET dogs/X/barks/Y
เช่นกัน
4. เปลือกไม้เรียกใช้คำสั่งระบบเพื่อดึงรหัสสุนัขรุ่นล่าสุดลงมาจาก Github จากนั้นจะส่งข้อความตัวอักษรเพื่อdog.owner
บอกพวกเขาว่ารหัสสุนัขตัวใหม่นั้นกำลังใช้งานอยู่
ถ้อยคำของสิ่งนี้มีความซับซ้อน แต่มันค่อนข้างเป็นงานอะซิงโครนัสที่เรียบง่าย:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
จากนั้นลูกค้าจะออกGET
s เพื่อ/v1/dogs/1/barks/a65h44
ทราบสถานะปัจจุบัน (ถ้ารหัสถูกดึงมันอีเมลจะถูกส่งไปยังเจ้าของและเช่นนั้น) เมื่อใดก็ตามที่มีการเปลี่ยนแปลงสุนัขที่303
เป็น appliable
ห่อ
การอ้างอิงRoy Fielding :
สิ่งเดียวที่ REST ต้องการสำหรับวิธีการคือพวกมันถูกกำหนดอย่างเท่าเทียมกันสำหรับทรัพยากรทั้งหมด (นั่นคือเพื่อให้ตัวกลางไม่จำเป็นต้องรู้ประเภทของทรัพยากรเพื่อทำความเข้าใจความหมายของคำขอ)
ในตัวอย่างข้างต้นPOST
ได้รับการออกแบบอย่างสม่ำเสมอ มันจะทำให้สุนัข " bark
" ไม่ปลอดภัย (หมายถึงเปลือกไม้มีผลกระทบต่อทรัพยากร) และ idempotent (คำขอแต่ละครั้งให้ผลลัพธ์ใหม่bark
) ซึ่งเหมาะกับPOST
คำกริยา
โปรแกรมเมอร์จะได้รู้ว่ากPOST
จะมีผลเป็นbarks
bark
รหัสสถานะการตอบกลับ (รวมถึงเอนทิตีร่างกายและส่วนหัวเมื่อจำเป็น) ทำหน้าที่อธิบายสิ่งที่เปลี่ยนแปลงและวิธีที่ไคลเอ็นต์สามารถและควรดำเนินการต่อ
หมายเหตุ: แหล่งที่มาหลักที่ใช้คือ: " พักผ่อน Web Services " หนังสือที่HTTP RFCและบล็อกรอยฝ่าย
แก้ไข:
คำถามและคำตอบจึงเปลี่ยนไปเล็กน้อยตั้งแต่สร้างครั้งแรก คำถามเดิมถามเกี่ยวกับการออกแบบของ URI เช่น:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
ด้านล่างนี้เป็นคำอธิบายว่าทำไมจึงไม่ใช่ตัวเลือกที่ดี:
วิธีลูกค้าบอกเซิร์ฟเวอร์ว่าจะทำอย่างไรกับข้อมูลที่เป็นข้อมูลวิธีการ
- บริการเว็บสงบเงียบถ่ายทอดข้อมูลวิธีการในวิธี HTTP
- บริการ RPC-Style และ SOAP ทั่วไปจะเก็บไว้ในส่วนหัวของเอนทิตีและ HTTP
ซึ่งเป็นส่วนหนึ่งของข้อมูล [ลูกค้าต้องการเซิร์ฟเวอร์] เพื่อใช้งานบนเป็นข้อมูลการกำหนดขอบเขต
- บริการ RESTful ใช้ URI บริการ SOAP / RPC-Style ใช้ส่วนหัวของเอนทิตีและ HTTP อีกครั้ง
เป็นตัวอย่างที่ใช้ http://www.google.com/search?q=DOG
URI มีข้อมูลวิธีการและข้อมูลการกำหนดขอบเขตคือGET
/search?q=DOG
เรื่องสั้นสั้น:
- ในสถาปัตยกรรม RESTfulข้อมูลวิธีการจะเข้าสู่วิธีการ HTTP
- ในสถาปัตยกรรมเชิงทรัพยากรข้อมูลการกำหนดขอบเขตจะเข้าสู่ URI
และกฎของหัวแม่มือ:
หากวิธี HTTP ไม่ตรงกับข้อมูลวิธีแสดงว่าบริการนั้นไม่สงบ หากข้อมูลการกำหนดขอบเขตไม่ได้อยู่ใน URI บริการจะไม่มุ่งเน้นทรัพยากร
คุณสามารถใส่"เปลือก" "การกระทำ"ใน URL (หรือในกิจการที่ร่างกาย) POST
และการใช้งาน ไม่มีปัญหามีการทำงานและอาจจะเป็นวิธีที่ง่ายที่สุดที่จะทำมันแต่ไม่สงบ
เพื่อให้บริการของคุณสงบเงียบจริงๆคุณอาจต้องย้อนกลับไปและคิดเกี่ยวกับสิ่งที่คุณต้องการทำที่นี่จริง ๆ (สิ่งที่จะมีผลกระทบต่อทรัพยากร)
ฉันไม่สามารถพูดคุยเกี่ยวกับความต้องการทางธุรกิจของคุณโดยเฉพาะ แต่ให้ฉันให้คุณตัวอย่างเช่น: example.com/order/123
พิจารณาการให้บริการสั่งซื้อสงบที่คำสั่งซื้อที่ยูริเหมือน
ตอนนี้บอกว่าเราต้องการยกเลิกคำสั่งซื้อเราจะทำอย่างไร หนึ่งอาจถูกล่อลวงไปคิดว่าเป็น"ยกเลิก" "การกระทำ"POST example.com/order/123?do=cancel
และการออกแบบเป็น
นั่นไม่สงบเช่นที่เราพูดไปข้างต้น แต่เราอาจจะPUT
เป็นตัวแทนคนใหม่ของorder
ที่มีcanceled
องค์ประกอบที่ส่งไปยังtrue
:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
และนั่นคือมัน หากไม่สามารถยกเลิกคำสั่งซื้อได้คุณสามารถส่งคืนรหัสสถานะเฉพาะได้ (การออกแบบแหล่งข้อมูลย่อยเช่นเดียวPOST /order/123/canceled
กับเอนทิตีร่างกายtrue
อาจเพื่อความเรียบง่ายนอกจากนี้ยังมี)
ในสถานการณ์เฉพาะของคุณคุณอาจลองทำสิ่งที่คล้ายกัน วิธีการที่ในขณะที่สุนัขเห่าเช่นGET
ที่/v1/dogs/1/
อาจรวมถึงข้อมูลที่(เช่น<barking>true</barking>
) หรือ ... ถ้ามันซับซ้อนเกินไปให้คลายความต้องการ RESTful และทำPOST
ตาม
ปรับปรุง:
ฉันไม่ต้องการให้คำตอบใหญ่เกินไป แต่ใช้เวลาสักครู่ในการหยุดการเปิดเผยอัลกอริทึม (การกระทำ ) เป็นชุดของทรัพยากร แทนที่จะคิดในแง่ของการกระทำ ( "ค้นหาสถานที่บนแผนที่" ) เราต้องคิดในแง่ของผลลัพธ์ของการกระทำนั้น ( "รายการสถานที่บนแผนที่ที่ตรงกับเกณฑ์การค้นหา" )
คุณอาจพบว่าตัวเองกลับมาที่ขั้นตอนนี้หากคุณพบว่าการออกแบบของคุณไม่พอดีกับอินเทอร์เฟซที่เป็นชุดของ HTTP
ตัวแปรแบบสอบถามมีการ กำหนดขอบเขตข้อมูลแต่ไม่ทรัพยากรใหม่แสดงว่า ( /post?lang=en
ชัดเจนเดียวกันทรัพยากรเป็น/post?lang=jp
เพียงแค่การแสดงที่แตกต่างกัน) แต่พวกเขาจะใช้ในการถ่ายทอดสถานะลูกค้า (เช่น?page=10
เพื่อที่รัฐจะไม่เก็บไว้ในเซิร์ฟเวอร์นั้น?lang=en
เป็นตัวอย่างที่นี่) หรือพารามิเตอร์ป้อนเข้ากับทรัพยากรอัลกอริทึม ( /search?q=dogs
, /dogs?code=1
) อีกครั้งทรัพยากรไม่แตกต่างกัน
คุณสมบัติ HTTP verbs '(methods):
อีกจุดที่ชัดเจนที่แสดง?action=something
ใน URI ไม่ใช่สงบเป็นคุณสมบัติของคำกริยา HTTP:
GET
และHEAD
ปลอดภัย (และ idempotent);
PUT
และDELETE
เป็น idempotent เท่านั้น
POST
ไม่ใช่
ความปลอดภัย : A GET
หรือHEAD
คำขอเป็นคำขอเพื่ออ่านข้อมูลบางอย่างไม่ใช่คำขอเพื่อเปลี่ยนสถานะเซิร์ฟเวอร์ใด ๆ ลูกค้าสามารถสร้างGET
หรือHEAD
ขอ 10 ครั้งและมันก็เหมือนกับการทำครั้งเดียวหรือไม่เคยทำเลย
Idempotence : การดำเนินการ idempotent ในที่มีผลเช่นเดียวกันไม่ว่าคุณจะใช้เพียงครั้งเดียวหรือมากกว่าหนึ่งครั้ง (ในทางคณิตศาสตร์การคูณด้วยศูนย์คือ idempotent) หากคุณDELETE
ใช้ทรัพยากรครั้งเดียวการลบอีกครั้งจะมีผลเช่นเดียวกัน (ทรัพยากรมีGONE
อยู่แล้ว)
POST
ไม่ปลอดภัยหรือ idempotent POST
การส่งคำขอที่เหมือนกันสองรายการไปยังทรัพยากร 'โรงงาน' อาจส่งผลให้มีทรัพยากรรองสองรายการที่มีข้อมูลเดียวกัน ด้วยการโอเวอร์โหลด (เมธอดใน URI หรือเอนทิตี้ของ) การPOST
เดิมพันทั้งหมดจะปิด
คุณสมบัติทั้งสองนี้มีความสำคัญต่อความสำเร็จของโปรโตคอล HTTP (ผ่านเครือข่ายที่ไม่น่าเชื่อถือ!): คุณอัปเดตGET
หน้าเว็บกี่ครั้งโดยไม่รอจนกว่าจะโหลดเต็ม
การสร้างการกระทำและการวางไว้ใน URL จะเป็นการแบ่งสัญญาของวิธี HTTP อย่างชัดเจน เทคโนโลยีช่วยให้คุณอีกครั้งคุณสามารถทำได้ แต่นั่นไม่ใช่การออกแบบที่สงบ