REST API ควรจัดการคำขอ PUT กับทรัพยากรที่สามารถแก้ไขได้บางส่วนได้อย่างไร


46

สมมติว่า REST API ตอบกลับGETคำขอHTTP ส่งคืนข้อมูลเพิ่มเติมบางอย่างในวัตถุย่อยowner:

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

เห็นได้ชัดว่าเราไม่ต้องการให้ใครPUTกลับมา

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

และประสบความสำเร็จ อันที่จริงเราอาจไม่ได้ใช้วิธีการที่อาจประสบความสำเร็จในกรณีนี้

แต่คำถามนี้ไม่เพียงเกี่ยวกับวัตถุย่อย: โดยทั่วไปแล้วจะทำอย่างไรกับข้อมูลที่ไม่ควรแก้ไขได้ในคำขอ PUT

มันควรจะต้องหายไปจากคำขอ PUT หรือไม่

มันควรจะถูกทิ้งอย่างเงียบ ๆ ?

ควรตรวจสอบหรือไม่และถ้ามันแตกต่างจากค่าเก่าของแอตทริบิวต์นั้นให้ส่งคืนรหัสข้อผิดพลาด HTTP ในการตอบกลับหรือไม่

หรือเราควรใช้แพตช์ RFC 6902 JSON แทนที่จะส่งทั้ง JSON?


2
ทั้งหมดนี้ใช้ได้ ฉันเดาว่ามันขึ้นอยู่กับความต้องการของคุณ
Robert Harvey

ฉันจะบอกว่าหลักการของความประหลาดใจน้อยที่สุดจะบ่งบอกว่ามันควรจะหายไปจากคำขอ PUT ถ้ามันเป็นไปไม่ได้แล้วให้ตรวจสอบว่ามันแตกต่างและกลับมาพร้อมรหัสข้อผิดพลาด การละทิ้งเงียบเป็นสิ่งที่เลวร้ายที่สุด (การส่งโดยผู้ใช้คาดหวังว่ามันจะเปลี่ยนไปและคุณจะบอกพวกเขาว่า "200 OK")
Maciej Piechotka

2
@MaciejPiechotka ปัญหากับที่คุณไม่ได้ใช้รูปแบบเดียวกันในการใส่เป็นแทรกหรือรับ ฯลฯ ฉันต้องการรุ่นเดียวกันรับใช้และมีกฎการอนุญาตฟิลด์ง่าย ๆ ดังนั้นหากพวกเขาป้อนค่าสำหรับ ฟิลด์ที่พวกเขาไม่ควรเปลี่ยนพวกเขาจะได้รับ 403 สิ่งต้องห้ามและในภายหลังเมื่อมีการตั้งค่าการอนุญาตให้อนุญาตพวกเขาจะได้รับ 401 ไม่ได้รับอนุญาตหากพวกเขาไม่ได้รับอนุญาต
Jimmy Hoffa

@JimmyHoffa: ตามแบบจำลองคุณหมายถึงรูปแบบข้อมูล (เพราะอาจเป็นไปได้ที่จะนำรูปแบบในกรอบ MVC Rest ขึ้นอยู่กับการเลือกใช้ถ้ามีการใช้ - OP ไม่ได้กล่าวถึงใด ๆ )? ฉันจะไปกับการค้นพบถ้าฉันไม่ได้ถูก จำกัด โดยกรอบและข้อผิดพลาดในช่วงต้นเป็นเล็กน้อยที่ค้นพบ / ใช้งานง่ายแล้วตรวจสอบการเปลี่ยนแปลง (ตกลง - ฉันไม่ควรสัมผัสฟิลด์ XYZ) ในกรณีใด ๆ การละทิ้งนั้นเลวร้ายที่สุด
Maciej Piechotka

คำตอบ:


46

มีกฎไม่เป็นทั้งในสเป็คของ W3C หรือกฎระเบียบที่ไม่เป็นทางการของส่วนที่เหลือที่บอกว่าPUTต้องใช้สคีเดียวกัน / GETรุ่นเป็นสอดคล้องกัน

มันดีถ้าพวกมันคล้ายกันแต่ก็ไม่แปลกที่PUTจะทำสิ่งต่าง ๆ เล็กน้อย ตัวอย่างเช่นฉันเห็น API จำนวนมากที่มี ID บางประเภทในเนื้อหาที่ส่งคืนโดย a GETเพื่อความสะดวก แต่ด้วย a PUTรหัสนั้นจะถูกกำหนดโดย URI เท่านั้นและไม่มีความหมายในเนื้อหา ID ใด ๆ ที่พบในร่างกายจะถูกเพิกเฉยอย่างเงียบ ๆ

ส่วนที่เหลือและเว็บโดยทั่วไปจะผูกติดอยู่กับหลักการความแข็งแกร่ง : "จงระมัดระวังในสิ่งที่คุณทำ [ส่ง] จงมีอิสระในสิ่งที่คุณยอมรับ" หากคุณเห็นด้วยกับสิ่งนี้แล้วการแก้ปัญหาก็ชัดเจน: ไม่ต้องสนใจข้อมูลที่ไม่ถูกต้องในPUTคำขอ ที่ใช้กับทั้งข้อมูลที่ไม่เปลี่ยนรูปเช่นในตัวอย่างของคุณและเรื่องไร้สาระจริงเช่นเขตข้อมูลที่ไม่รู้จัก

PATCHอาจเป็นอีกตัวเลือกหนึ่ง แต่คุณไม่ควรนำไปใช้PATCHนอกเสียจากว่าคุณจะสนับสนุนการอัปเดตบางส่วน PATCHหมายถึงอัปเดตเฉพาะแอตทริบิวต์ที่ฉันรวมไว้ในเนื้อหาเท่านั้น มันไม่ได้หมายความว่าแทนนิติบุคคลทั้งหมด แต่ไม่รวมเขตข้อมูลบางอย่าง สิ่งที่คุณกำลังพูดถึงไม่ใช่การอัพเดทบางส่วน แต่เป็นการอัพเดทเต็มรูปแบบ idempotent และทั้งหมดเป็นเพียงส่วนหนึ่งของทรัพยากรที่เป็นแบบอ่านอย่างเดียว

สิ่งที่น่าทำถ้าคุณเลือกตัวเลือกนี้จะเป็นการส่งกลับ 200 (OK) ด้วยเอนทิตีที่อัพเดตจริงในการตอบกลับเพื่อให้ลูกค้าสามารถเห็นได้อย่างชัดเจนว่าฟิลด์แบบอ่านอย่างเดียวไม่ได้ถูกอัพเดต

มีบางคนที่คิดอย่างอื่นว่าควรเป็นข้อผิดพลาดในการพยายามปรับปรุงส่วนทรัพยากรแบบอ่านอย่างเดียว มีเหตุผลบางประการเกี่ยวกับเรื่องนี้โดยพื้นฐานแล้วคุณจะต้องส่งคืนข้อผิดพลาดอย่างแน่นอนหากทรัพยากรทั้งหมดเป็นแบบอ่านอย่างเดียวและผู้ใช้พยายามอัปเดต มันขัดกับหลักการความทนทานอย่างแน่นอน แต่คุณอาจคิดว่ามันเป็น "การทำเอกสารด้วยตนเอง" มากกว่าสำหรับผู้ใช้ API ของคุณ

มีสองอนุสัญญาสำหรับเรื่องนี้ซึ่งทั้งสองสอดคล้องกับแนวคิดดั้งเดิมของคุณ แต่ฉันจะขยายพวกเขา วิธีแรกคือการห้ามไม่ให้ฟิลด์แบบอ่านอย่างเดียวปรากฏในเนื้อหาและส่งคืน HTTP 400 (คำขอไม่ถูกต้อง) หากมี API ของการเรียงลำดับนี้ควรส่งคืน HTTP 400 หากมีฟิลด์อื่นที่ไม่รู้จัก / ใช้ไม่ได้ อย่างที่สองคือต้องการให้ฟิลด์แบบอ่านอย่างเดียวเหมือนกับเนื้อหาปัจจุบันและส่งคืน 409 (ความขัดแย้ง) หากค่าไม่ตรงกัน

ผมไม่ชอบการตรวจสอบความเท่าเทียมกันกับ 409 เพราะมันคงเส้นคงวาต้องของลูกค้าที่จะทำเพื่อดึงข้อมูลปัจจุบันก่อนที่จะสามารถที่จะทำGET PUTนั่นไม่ดีและอาจนำไปสู่ประสิทธิภาพที่ไม่ดีสำหรับบางคนที่ไหนสักแห่ง ฉันยังจริงๆไม่ชอบ 403 (Forbidden) สำหรับนี้มันหมายถึงว่าทั้งทรัพยากรที่มีการป้องกันไม่ได้เป็นเพียงส่วนหนึ่งของมัน ดังนั้นความเห็นของฉันคือถ้าคุณต้องตรวจสอบอย่างแน่นอนแทนที่จะทำตามหลักการความแข็งแกร่งให้ตรวจสอบคำขอทั้งหมดของคุณและส่งคืน 400 สำหรับสิ่งที่มีเขตข้อมูลพิเศษหรือไม่สามารถเขียนได้

ตรวจสอบให้แน่ใจว่า 400/409 ของคุณรวมถึงข้อมูลเกี่ยวกับปัญหาที่เกิดขึ้นและวิธีการแก้ไข

วิธีการทั้งสองนี้ใช้ได้ แต่ฉันชอบวิธีเก่ากว่าเพื่อให้สอดคล้องกับหลักการความทนทาน หากคุณเคยมีประสบการณ์ทำงานกับ REST API ขนาดใหญ่คุณจะประทับใจกับคุณค่าของความเข้ากันได้แบบย้อนหลัง หากคุณตัดสินใจที่จะลบเขตข้อมูลที่มีอยู่หรือทำให้เป็นแบบอ่านอย่างเดียวมันเป็นการเปลี่ยนแปลงที่เข้ากันได้ย้อนหลังหากเซิร์ฟเวอร์เพียงละเว้นเขตข้อมูลเหล่านั้นและลูกค้าเก่าจะยังคงทำงาน อย่างไรก็ตามหากคุณทำการตรวจสอบความถูกต้องของเนื้อหาอย่างเข้มงวดจะไม่สามารถใช้งานร่วมกับผู้อื่นได้อีกต่อไปและลูกค้าเก่าจะหยุดทำงาน อดีตโดยทั่วไปหมายถึงการทำงานน้อยลงสำหรับทั้งผู้ดูแล API และลูกค้า


1
คำตอบที่ดีและ upvoted อย่างไรก็ตามฉันไม่แน่ใจว่าฉันเห็นด้วยกับเรื่องนี้: "หากคุณเคยตัดสินใจที่จะลบฟิลด์ที่มีอยู่หรือทำให้มันเป็นแบบอ่านอย่างเดียวมันเป็นการเปลี่ยนแปลงที่เข้ากันได้ย้อนหลังหากเซิร์ฟเวอร์เพียงละเว้นเขตข้อมูลเหล่านั้นและลูกค้าเก่าจะยังคงทำงาน " หากลูกค้าใช้ฟิลด์ที่ถูกลบ / เพิ่งอ่านอย่างเดียวนี่จะไม่ส่งผลกระทบต่อพฤติกรรมโดยรวมของแอพใช่หรือไม่ ในกรณีของการลบฟิลด์ฉันจะยืนยันว่ามันอาจจะดีกว่าที่จะสร้างข้อผิดพลาดอย่างชัดเจนแทนที่จะละเลยข้อมูล; มิฉะนั้นลูกค้าจะไม่ทราบว่าการอัปเดตที่ทำงานก่อนหน้านี้ล้มเหลว
rinogo

คำตอบนี้ผิด สำหรับ 2 เหตุผลจาก RFC2616: 1. (ส่วน 9.1.2) PUT จะต้องมีความเป็นอิสระ ใส่หลายครั้งและมันจะให้ผลลัพธ์เช่นเดียวกับการใส่เพียงครั้งเดียว 2. การเข้าถึงทรัพยากรควรส่งคืนเอนทิตีที่ใส่หากไม่มีการร้องขออื่น ๆ เพื่อทำการเปลี่ยนแปลงทรัพยากร
brunoais

1
จะทำอย่างไรถ้าคุณทำการตรวจสอบความเท่าเทียมกันเฉพาะในกรณีที่ส่งค่าไม่เปลี่ยนรูปในคำขอ ฉันคิดว่านี่เป็นสิ่งที่ดีที่สุดสำหรับคุณในสองโลก คุณไม่ได้บังคับให้ลูกค้าทำการ GET และคุณยังคงแจ้งให้ลูกค้าทราบว่ามีบางอย่างผิดปกติหากพวกเขาส่งค่าที่ไม่ถูกต้องสำหรับการเปลี่ยนรูปแบบ
Ahmad Abdelghany

ขอบคุณการเปรียบเทียบเชิงลึกที่คุณทำในย่อหน้าสุดท้ายที่มาจากประสบการณ์นั้นเป็นสิ่งที่ฉันกำลังมองหา
dhill

9

ความแรงของ Idem

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

หากคุณอนุญาตการอัปเดตบางส่วนจะไม่สามารถใช้งานได้อีกต่อไป หากคุณมีลูกค้าสองราย ลูกค้า A และ B จากนั้นสถานการณ์จำลองต่อไปนี้สามารถพัฒนาได้:

ไคลเอ็นต์ A รับรูปภาพจากอิมเมจทรัพยากร สิ่งนี้มีคำอธิบายของภาพซึ่งยังคงใช้ได้ ไคลเอนต์ B สร้างอิมเมจใหม่และอัพเดตคำอธิบายตามนั้น รูปภาพมีการเปลี่ยนแปลง เห็นลูกค้าเขาไม่จำเป็นต้องเปลี่ยนคำอธิบายเพราะมันเป็นตามที่เขาต้องการและใส่ภาพเท่านั้น

สิ่งนี้จะนำไปสู่ความไม่สอดคล้องกันของภาพมีการแนบข้อมูลเมตาที่ไม่ถูกต้อง!

ที่น่ารำคาญยิ่งกว่าคือคนกลางใด ๆ ที่สามารถทำซ้ำการร้องขอ ในกรณีที่ตัดสินใจอย่างใดอย่างหนึ่งล้มเหลว

ความหมายของ PUT ไม่สามารถเปลี่ยนแปลงได้ (แม้ว่าคุณจะใช้ผิดวัตถุประสงค์)

ตัวเลือกอื่น

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

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

และเซิร์ฟเวอร์สามารถตอบกลับด้วย 204 (ไม่มีเนื้อหา) เพื่อตั้งค่าสถานะความสำเร็จ ในข้อผิดพลาดคุณไม่สามารถอัปเดตส่วนหนึ่งของโครงสร้างได้ วิธีการแพทช์เป็นอะตอม

ข้อเสียของวิธีนี้คือเบราว์เซอร์บางตัวไม่รองรับสิ่งนี้ แต่นี่เป็นตัวเลือกที่เป็นธรรมชาติที่สุดใน REST-service

ตัวอย่างคำขอการแก้ไข: http://tools.ietf.org/html/rfc5789#section-2.1

การปะ Json

ดูเหมือนว่าตัวเลือก json นั้นค่อนข้างครอบคลุมและเป็นตัวเลือกที่น่าสนใจ แต่มันอาจเป็นเรื่องยากที่จะใช้สำหรับบุคคลที่สาม คุณต้องตัดสินใจว่าฐานผู้ใช้ของคุณสามารถจัดการกับสิ่งนี้ได้หรือไม่

มันค่อนข้างซับซ้อนเช่นกันเนื่องจากคุณจำเป็นต้องสร้างล่ามขนาดเล็กซึ่งแปลงคำสั่งเป็นโครงสร้างบางส่วนซึ่งคุณจะใช้เพื่ออัปเดตโมเดลของคุณ ล่ามนี้ควรตรวจสอบด้วยหากคำสั่งที่ให้มาเหมาะสม คำสั่งบางคำสั่งยกเลิกซึ่งกันและกัน (เขียน fielda ลบ fielda) ฉันคิดว่าคุณต้องการรายงานเรื่องนี้กลับไปยังลูกค้าเพื่อ จำกัด เวลาแก้ไขข้อบกพร่องทางด้านของเขา / เธอ

แต่ถ้าคุณมีเวลานี่เป็นทางออกที่สง่างามจริงๆ คุณควรตรวจสอบความถูกต้องของฟิลด์ต่างๆ คุณสามารถรวมสิ่งนี้กับวิธี PATCH เพื่ออยู่ในรูปแบบ REST แต่ฉันคิดว่า POST น่าจะยอมรับได้ที่นี่

จะไม่ดี

หากคุณตัดสินใจที่จะไปกับตัวเลือก PUT ซึ่งค่อนข้างเสี่ยง อย่างน้อยคุณควรจะไม่ทิ้งข้อผิดพลาด ผู้ใช้มีความคาดหวังบางอย่าง (ข้อมูลจะได้รับการอัปเดต) และถ้าคุณทำสิ่งนี้ได้คุณจะให้นักพัฒนาบางคนไม่ได้มีเวลาที่ดี

คุณสามารถเลือกสำหรับการตั้งค่าสถานะกลับ: 409 ความขัดแย้งหรือ 403 ต้องห้าม ขึ้นอยู่กับว่าคุณดูกระบวนการอัพเดตอย่างไร หากคุณเห็นว่ามันเป็นชุดของกฎ (ระบบเป็นศูนย์กลาง) ความขัดแย้งจะดีกว่า บางอย่างเช่นฟิลด์เหล่านี้ไม่สามารถอัปเดตได้ (ขัดแย้งกับกฎ) หากคุณเห็นว่ามันเป็นปัญหาการอนุญาต (ผู้ใช้เป็นศูนย์กลาง) จากนั้นคุณควรกลับห้าม ด้วย: คุณไม่ได้รับอนุญาตให้เปลี่ยนฟิลด์เหล่านี้

คุณยังควรบังคับให้ผู้ใช้ส่งฟิลด์ที่แก้ไขได้ทั้งหมด

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

ความเห็นส่วนตัว

โดยส่วนตัวแล้วฉันจะไป (ถ้าคุณไม่ต้องทำงานกับเบราว์เซอร์) สำหรับโมเดล PATCH แบบง่าย ๆ และต่อมาก็ขยายด้วยโปรเซสเซอร์แพตช์ JSON สิ่งนี้สามารถทำได้โดยการแยกความแตกต่างบน mimetypes: ชนิด mime ของ json patch:

แอพลิเคชัน / JSON แพทช์

และ json: application / json-patch

ทำให้ง่ายต่อการใช้งานในสองขั้นตอน


3
ตัวอย่าง idempotency ของคุณไม่สมเหตุสมผล ไม่ว่าคุณจะเปลี่ยนคำอธิบายหรือไม่ ไม่ว่าจะด้วยวิธีใดคุณจะได้รับผลลัพธ์เดียวกันทุกครั้ง
Robert Harvey

1
คุณคิดถูกแล้วฉันถึงเวลาเข้านอนแล้ว ฉันไม่สามารถแก้ไขได้ เป็นตัวอย่างเพิ่มเติมเกี่ยวกับเหตุผลของการส่งข้อมูลทั้งหมดในคำขอ PUT ขอบคุณสำหรับตัวชี้
Edgar Klerks

ฉันรู้ว่านี่เป็น 3 ปีที่ผ่านมา ... แต่คุณรู้หรือไม่ว่าใน RFC ฉันสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับ "PUT จะต้องส่งมอบวัตถุเต็มไปยังทรัพยากร" ฉันเคยเห็นสิ่งนี้มาแล้วที่อื่น แต่อยากจะดูว่ามันถูกกำหนดไว้ในสเป็คอย่างไร
CSharper

ฉันคิดว่าฉันพบมัน? tools.ietf.org/html/rfc5789#page-3
CSharper
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.