API REST nên xử lý các yêu cầu PUT như thế nào đối với các tài nguyên có thể sửa đổi một phần?


46

Giả sử API REST, đáp ứng GETyêu cầu HTTP , trả về một số dữ liệu bổ sung trong đối tượng phụ owner:

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

Rõ ràng, chúng tôi không muốn bất cứ ai có thể PUTtrở lại

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

và có thành công đó. Thật vậy, có lẽ chúng ta sẽ không thực hiện một cách để điều đó thậm chí có khả năng thành công, trong trường hợp này.

Nhưng câu hỏi này không chỉ là về các đối tượng phụ: nói chung, nên làm gì với dữ liệu không thể sửa đổi trong yêu cầu PUT?

Có nên yêu cầu thiếu trong yêu cầu PUT không?

Có nên âm thầm loại bỏ?

Nó có nên được kiểm tra không và nếu nó khác với giá trị cũ của thuộc tính đó, hãy trả về mã lỗi HTTP trong phản hồi?

Hoặc chúng ta nên sử dụng các bản vá JSON RFC 6902 thay vì gửi toàn bộ JSON?


2
Tất cả những thứ này sẽ làm việc. Tôi đoán nó phụ thuộc vào yêu cầu của bạn.
Robert Harvey

Tôi muốn nói rằng nguyên tắc ít bất ngờ nhất sẽ chỉ ra rằng nó nên bị thiếu trong yêu cầu PUT. Nếu không thể thì kiểm tra và nếu nó khác và trả về với mã lỗi. Loại bỏ im lặng là tồi tệ nhất (người dùng gửi đang hy vọng rằng nó sẽ thay đổi và bạn nói với họ "200 OK").
Maciej Piechotka

2
@MaciejPiechotka, vấn đề là bạn không được sử dụng cùng một mô hình trên put như khi chèn hoặc get, v.v. Tôi thích mô hình tương tự được sử dụng và có các quy tắc ủy quyền trường đơn giản vì vậy nếu chúng nhập một giá trị cho một lĩnh vực họ không nên thay đổi, họ lấy lại 403 Cấm và nếu sau đó, ủy quyền được thiết lập để cho phép họ nhận được một trái phép 401 nếu họ không được ủy quyền
Jimmy Hoffa

@JimmyHoffa: Theo mô hình, bạn có nghĩa là định dạng dữ liệu (vì có thể sử dụng lại mô hình trong khung MVC Rest tùy thuộc vào lựa chọn của nó, nếu có được sử dụng - OP không đề cập đến bất kỳ)? Tôi sẽ đi cùng với khả năng khám phá nếu tôi không bị ràng buộc bởi khung và lỗi sớm sẽ dễ phát hiện hơn / dễ thực hiện hơn sau đó kiểm tra thay đổi (ok - Tôi không nên chạm vào trường XYZ). Trong mọi trường hợp vứt bỏ là tồi tệ nhất.
Maciej Piechotka

Câu trả lời:


46

Không có quy tắc nào, trong thông số W3C hoặc quy tắc không chính thức của REST, nói rằng PUTphải sử dụng cùng một lược đồ / mô hình tương ứng GET.

Thật tuyệt nếu chúng giống nhau , nhưng không có gì lạ PUTkhi làm mọi thứ hơi khác. Ví dụ: tôi đã thấy rất nhiều API bao gồm một số loại ID trong nội dung được trả về bởi một GETcách thuận tiện. Nhưng với a PUT, ID đó được xác định riêng bởi URI và không có ý nghĩa trong nội dung. Bất kỳ ID nào được tìm thấy trong cơ thể sẽ được âm thầm bỏ qua.

REST và web nói chung gắn chặt với Nguyên tắc Mạnh mẽ : "Hãy thận trọng trong những gì bạn làm [gửi], hãy tự do trong những gì bạn chấp nhận." Nếu bạn đồng ý về mặt triết học với điều này, thì giải pháp là hiển nhiên: Bỏ qua mọi dữ liệu không hợp lệ trong PUTcác yêu cầu. Điều đó áp dụng cho cả dữ liệu bất biến, như trong ví dụ của bạn và thực tế vô nghĩa, ví dụ như các trường chưa biết.

PATCHcó khả năng là một tùy chọn khác, nhưng bạn không nên thực hiện PATCHtrừ khi bạn thực sự sẽ hỗ trợ cập nhật một phần. PATCHcó nghĩa là chỉ cập nhật các thuộc tính cụ thể mà tôi đưa vào nội dung ; nó không có nghĩa là thay thế toàn bộ thực thể nhưng loại trừ một số trường cụ thể . Những gì bạn đang thực sự nói về không thực sự là một bản cập nhật một phần, đó là một bản cập nhật đầy đủ, bình thường và tất cả, đó chỉ là một phần của tài nguyên là chỉ đọc.

Một điều tốt đẹp để làm nếu bạn chọn tùy chọn này là gửi lại 200 (OK) với thực thể được cập nhật thực tế trong phản hồi, để khách hàng có thể thấy rõ rằng các trường chỉ đọc không được cập nhật.

Chắc chắn có một số người nghĩ theo cách khác - đó là lỗi khi cố cập nhật phần chỉ đọc của tài nguyên. Có một số biện minh cho điều này, chủ yếu dựa trên cơ sở rằng bạn chắc chắn sẽ trả về lỗi nếu toàn bộ tài nguyên ở chế độ chỉ đọc và người dùng đã cố cập nhật nó. Nó chắc chắn đi ngược lại nguyên tắc mạnh mẽ, nhưng bạn có thể coi nó là "tài liệu tự" hơn cho người dùng API của bạn.

Có hai quy ước cho việc này, cả hai đều tương ứng với ý tưởng ban đầu của bạn, nhưng tôi sẽ mở rộng chúng. Đầu tiên là cấm các trường chỉ đọc xuất hiện trong nội dung và trả lại HTTP 400 (Yêu cầu xấu) nếu chúng xảy ra. Các API thuộc loại này cũng sẽ trả về HTTP 400 nếu có bất kỳ trường nào không được nhận dạng / không sử dụng được. Thứ hai là yêu cầu các trường chỉ đọc phải giống hệt với nội dung hiện tại và trả về 409 (Xung đột) nếu các giá trị không khớp.

Tôi thực sự không thích kiểm tra đẳng thức với 409 bởi vì nó luôn đòi hỏi khách hàng phải thực hiện GETđể lấy dữ liệu hiện tại trước khi có thể thực hiện a PUT. Điều đó không tốt và có lẽ sẽ dẫn đến hiệu suất kém, đối với ai đó, ở đâu đó. Tôi cũng thực sự không thích 403 (Cấm) vì điều này ngụ ý rằng toàn bộ tài nguyên được bảo vệ, không chỉ là một phần của nó. Vì vậy, ý kiến ​​của tôi là, nếu bạn hoàn toàn phải xác nhận thay vì tuân theo nguyên tắc mạnh mẽ, hãy xác thực tất cả các yêu cầu của bạn và trả lại 400 cho bất kỳ trường nào có thêm trường hoặc không thể ghi.

Đảm bảo 400/409 / bất cứ điều gì của bạn bao gồm thông tin về vấn đề cụ thể là gì và cách khắc phục.

Cả hai cách tiếp cận này đều hợp lệ, nhưng tôi thích phương pháp trước phù hợp với nguyên tắc mạnh mẽ. Nếu bạn đã từng có kinh nghiệm làm việc với API REST lớn, bạn sẽ đánh giá cao giá trị của khả năng tương thích ngược. Nếu bạn từng quyết định xóa một trường hiện có hoặc làm cho nó ở chế độ chỉ đọc, thì đó là một thay đổi tương thích ngược nếu máy chủ chỉ bỏ qua các trường đó và các máy khách cũ vẫn hoạt động. Tuy nhiên, nếu bạn thực hiện xác nhận nghiêm ngặt về nội dung, nó không còn tương thích ngược nữa và các máy khách cũ sẽ ngừng hoạt động. Cái trước thường có nghĩa là làm việc ít hơn cho cả người duy trì API và khách hàng của nó.


1
Câu trả lời tốt, và nâng cao. Tuy nhiên, tôi không chắc chắn tôi đồng ý với điều này: "Nếu bạn từng quyết định xóa một trường hiện có hoặc làm cho nó ở chế độ chỉ đọc, thì đó là một thay đổi tương thích ngược nếu máy chủ chỉ bỏ qua các trường đó và các máy khách cũ vẫn hoạt động. " Nếu khách hàng dựa vào trường bị xóa / chỉ đọc mới này, liệu điều này có còn ảnh hưởng đến hành vi chung của ứng dụng không? Trong trường hợp xóa các trường, tôi cho rằng có thể tốt hơn là tạo ra lỗi thay vì bỏ qua dữ liệu; mặt khác, khách hàng không biết rằng bản cập nhật hoạt động trước đó của nó hiện đang bị lỗi.
rinogo

Câu trả lời này là sai. vì 2 lý do từ RFC2616: 1. (phần 9.1.2) PUT phải độc lập. Đặt nhiều lần và nó sẽ tạo ra kết quả tương tự như chỉ đặt một lần. 2. Việc nhận tài nguyên sẽ trả lại thực thể nếu không có yêu cầu nào khác được thực hiện để thay đổi tài nguyên
brunoais

1
Điều gì sẽ xảy ra nếu bạn thực hiện kiểm tra đẳng thức chỉ khi giá trị bất biến được gửi trong yêu cầu. Tôi nghĩ rằng điều này mang lại cho bạn tốt nhất của hai thế giới; bạn không ép buộc khách hàng thực hiện NHẬN và bạn vẫn thông báo cho họ biết có gì đó không ổn nếu họ gửi giá trị không hợp lệ cho một bất biến.
Ahmad Abdelghany

Cảm ơn, so sánh sâu mà bạn đã làm trong các đoạn cuối đến từ kinh nghiệm là chính xác những gì tôi đang tìm kiếm.
doping

9

Hiệu lực của Idem

Theo RFC, một PUT sẽ phải cung cấp toàn bộ đối tượng cho tài nguyên. Lý do chính của điều này, là PUT nên bình thường. Điều này có nghĩa là một yêu cầu, được lặp đi lặp lại sẽ đánh giá cùng một kết quả trên máy chủ.

Nếu bạn cho phép cập nhật một phần, nó không thể hoạt động mạnh nữa. Nếu bạn có hai khách hàng. Máy khách A và B, sau đó kịch bản sau đây có thể phát triển:

Khách hàng A có được một hình ảnh từ hình ảnh tài nguyên. Điều này có chứa một mô tả của hình ảnh, vẫn còn hiệu lực. Khách hàng B đặt một hình ảnh mới và cập nhật mô tả cho phù hợp. Bức tranh đã thay đổi. Khách hàng A thấy, anh ta không phải thay đổi mô tả, vì đó là như anh ta muốn và chỉ đặt hình ảnh.

Điều này sẽ dẫn đến sự không nhất quán, hình ảnh có siêu dữ liệu sai được đính kèm!

Khó chịu hơn nữa là bất kỳ trung gian nào cũng có thể lặp lại yêu cầu. Trong trường hợp nó quyết định bằng cách nào đó PUT thất bại.

Ý nghĩa của PUT không thể thay đổi (mặc dù bạn có thể sử dụng sai nó).

Sự lựa chọn khác

May mắn thay, có một lựa chọn khác, đây là VÒI. PATCH là một phương thức cho phép bạn cập nhật một phần cấu trúc. Bạn chỉ có thể gửi một cấu trúc một phần. Đối với các ứng dụng đơn giản, điều này là tốt. Phương pháp này không được đảm bảo là idem mạnh. Khách hàng nên gửi yêu cầu theo mẫu sau:

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

Và máy chủ có thể trả lời lại với 204 (Không có nội dung) để gắn cờ thành công. Khi có lỗi, bạn không thể cập nhật một phần của cấu trúc. Phương pháp PATCH là nguyên tử.

Nhược điểm của phương pháp này là, không phải tất cả các trình duyệt đều hỗ trợ điều này, nhưng đây là tùy chọn tự nhiên nhất trong dịch vụ REST.

Yêu cầu vá ví dụ: http://tools.ietf.org/html/rfc5789#section-2.1

Json vá

Tùy chọn json dường như khá toàn diện và là một tùy chọn thú vị. Nhưng nó có thể khó thực hiện cho các bên thứ ba. Bạn phải quyết định xem cơ sở người dùng của bạn có thể xử lý việc này không.

Nó cũng hơi phức tạp, bởi vì bạn cần xây dựng một trình thông dịch nhỏ để chuyển đổi các lệnh thành một cấu trúc một phần, mà bạn sẽ sử dụng để cập nhật mô hình của mình. Trình thông dịch này cũng nên kiểm tra, nếu các lệnh được cung cấp có ý nghĩa. Một số lệnh triệt tiêu lẫn nhau. (viết fielda, xóa fielda). Tôi nghĩ rằng bạn muốn báo cáo lại điều này cho khách hàng để hạn chế thời gian gỡ lỗi về phía anh ấy / cô ấy.

Nhưng nếu bạn có thời gian, đây là một giải pháp thực sự thanh lịch. Bạn vẫn nên xác nhận các lĩnh vực của khóa học. Bạn có thể kết hợp điều này với phương thức PATCH để ở trong mô hình REST. Nhưng tôi nghĩ POST sẽ được chấp nhận ở đây.

Xu hướng tồi đi

Nếu bạn quyết định chọn tùy chọn PUT, điều này có phần rủi ro. Sau đó, bạn ít nhất không nên loại bỏ lỗi. Người dùng có một kỳ vọng nhất định (dữ liệu sẽ được cập nhật) và nếu bạn phá vỡ điều này, bạn sẽ cung cấp cho một số nhà phát triển không phải là thời điểm tốt.

Bạn có thể chọn gắn cờ trở lại: 409 Xung đột hoặc 403 Bị cấm. Nó phụ thuộc vào cách bạn nhìn vào quá trình cập nhật. Nếu bạn thấy nó là một bộ quy tắc (hệ thống trung tâm), thì xung đột sẽ tốt hơn. Một cái gì đó như, các lĩnh vực này không thể cập nhật. (Xung đột với các quy tắc). Nếu bạn thấy đó là một vấn đề ủy quyền (lấy người dùng làm trung tâm), thì bạn nên quay lại bị cấm. Với: bạn không được phép thay đổi các trường này.

Bạn vẫn nên buộc người dùng gửi tất cả các trường có thể sửa đổi.

Một tùy chọn hợp lý để thực thi điều này là đặt nó thành tài nguyên phụ, chỉ cung cấp dữ liệu có thể sửa đổi.

Ý kiến ​​cá nhân

Cá nhân tôi sẽ đi (nếu bạn không phải làm việc với các trình duyệt) cho mô hình PATCH đơn giản và sau đó mở rộng nó bằng bộ xử lý vá JSON. Điều này có thể được thực hiện bằng cách phân biệt các mimetypes: Loại mime của bản vá json:

ứng dụng / json-patch

Và json: ứng dụng / json-patch

làm cho nó dễ dàng để thực hiện nó trong hai giai đoạn.


3
Ví dụ về sự bình tĩnh của bạn không có ý nghĩa. Hoặc bạn thay đổi mô tả hoặc bạn không. Dù bằng cách nào, bạn sẽ nhận được cùng một kết quả, mọi lúc.
Robert Harvey

1
Bạn nói đúng, tôi nghĩ đã đến lúc đi ngủ. Tôi không thể chỉnh sửa nó. Đây là một ví dụ về lý do gửi tất cả dữ liệu trong yêu cầu PUT. Cảm ơn con trỏ.
Edgar Klerks

Tôi biết điều này đã 3 năm trước ... nhưng bạn có biết RFC ở đâu trong tôi có thể tìm thêm thông tin về "PUT sẽ phải cung cấp đầy đủ đối tượng cho tài nguyên." Tôi đã thấy điều này được đề cập ở nơi khác nhưng muốn xem nó được định nghĩa như thế nào trong thông số kỹ thuật.
CSharper

Tôi nghĩ rằng tôi đã tìm thấy nó? tools.ietf.org/html/rfc5789#page-3
CSharper
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.