Kode status HTTP apa yang akan dikembalikan jika beberapa tindakan selesai dengan status yang berbeda?

72

Saya sedang membangun API di mana pengguna dapat meminta server untuk melakukan beberapa tindakan dalam satu permintaan HTTP. Hasilnya dikembalikan sebagai array JSON, dengan satu entri per tindakan.

Masing-masing tindakan ini mungkin gagal atau berhasil secara independen satu sama lain. Misalnya, tindakan pertama mungkin berhasil, input ke tindakan kedua mungkin tidak diformat dengan baik dan gagal divalidasi dan tindakan ketiga mungkin menyebabkan kesalahan yang tidak terduga.

Jika ada satu permintaan per tindakan, saya akan mengembalikan kode status masing-masing 200, 422 dan 500. Tetapi sekarang ketika hanya ada satu permintaan, kode status apa yang harus saya kembalikan?

Beberapa opsi:

  • Selalu kembalikan 200, dan berikan informasi lebih rinci di badan.
  • Mungkin ikuti aturan di atas hanya ketika ada lebih dari satu tindakan dalam permintaan?
  • Mungkin mengembalikan 200 jika semua permintaan berhasil, jika tidak 500 (atau kode lain)?
  • Cukup gunakan satu permintaan per tindakan, dan terima overhead tambahan.
  • Sesuatu yang sama sekali berbeda?
Anders
sumber
3
Pertanyaan Anda membuat saya berpikir tentang yang lain: programmers.stackexchange.com/questions/309147/…
AilurusFulgens
7
Juga sedikit terkait: programmers.stackexchange.com/questions/305250/… (lihat jawaban yang diterima tentang pemisahan antara kode status HTTP dan kode aplikasi)
4
Apa keuntungan yang Anda raih dengan mengelompokkan permintaan itu bersama? Apakah ini tentang logika bisnis, seperti transaksi dengan banyak sumber daya, atau ini tentang kinerja? Atau sesuatu yang lain?
Luc Franken
5
Ok, dalam hal ini saya sangat menyarankan untuk meningkatkan kinerja di bidang lain. Cobalah hal-hal seperti optimis ui, permintaan batching, caching dll sebelum mengimplementasikan kompleksitas ini ke dalam lapisan bisnis Anda. Apakah Anda memiliki wawasan yang jelas tentang di mana Anda kehilangan banyak waktu?
Luc Franken
4
... jangan terlalu berharap bahwa orang akan dengan benar melihat status itu. Sebagian besar program hanya memeriksa yang paling umum dan gagal atau berperilaku salah jika mereka mendapatkan kode status yang tidak terduga. (Saya ingat ada juga presentasi di DefCon tentang melindungi situs Anda dari perayap dengan mengirimkan status keluar acak yang diabaikan oleh peramban dan hanya menampilkan mengapa perayap terkadang dianggap sebagai kesalahan dan dengan demikian berhenti merayapi bagian situs web Anda itu).
Bakuriu

Jawaban:

21

Jawaban singkat dan langsung

Karena permintaan berbicara tentang mengeksekusi daftar tugas (tugas adalah sumber yang kita bicarakan di sini), maka jika grup tugas telah dipindahkan ke depan untuk dieksekusi (yaitu, terlepas dari hasil eksekusi), maka akan masuk akal bahwa status respons akan 200 OK. Kalau tidak, jika ada masalah yang akan mencegah eksekusi kelompok tugas, seperti gagal validasi objek tugas , atau beberapa layanan yang diperlukan tidak tersedia misalnya, maka status respons harus menunjukkan kesalahan itu. Melewati itu, ketika pelaksanaan tugas dimulai, mengingat tugas yang harus dilakukan tercantum dalam badan permintaan, maka saya akan berharap bahwa hasil eksekusi akan terdaftar di badan respons.


Panjang, jawaban filosofis

Anda mengalami dilema ini karena Anda mengalihkan dari apa HTTP dirancang untuk. Anda tidak berinteraksi untuk mengelola sumber daya, melainkan menggunakannya sebagai cara pemanggilan metode jarak jauh (yang tidak terlalu aneh, namun bekerja dengan buruk tanpa skema yang ditentukan sebelumnya).

Dengan kata-kata di atas, dan tanpa keberanian untuk mengubah jawaban ini menjadi panduan lama, berikut ini adalah skema URI yang sesuai dengan pendekatan manajemen sumber daya:

  • /tasks
    • GET daftar semua tugas, diberi nomor halaman
    • POST menambahkan satu tugas
  • /tasks/task/[id]
    • GET merespons dengan objek keadaan tugas tunggal
    • DELETE membatalkan / menghapus tugas
  • /tasks/groups
    • GET daftar semua grup tugas, diberi nomor halaman
    • POST menambahkan sekelompok tugas
  • /tasks/groups/group/[id]
    • GET merespons dengan status grup tugas
    • DELETE membatalkan / menghapus grup tugas

Struktur ini berbicara tentang sumber daya, bukan apa yang harus dilakukan dengan mereka. Apa yang dilakukan dengan sumber daya adalah masalah layanan lain.

Poin penting lain yang harus dibuat adalah disarankan untuk tidak memblokir terlalu lama dalam penangan permintaan HTTP. Sama seperti UI, antarmuka HTTP harus responsif - dalam skala waktu yang beberapa kali lipat lebih lambat (karena lapisan ini berhubungan dengan IO).

Membuat langkah ke arah merancang antarmuka HTTP yang secara ketat mengelola sumber daya mungkin sama sulitnya dengan memindahkan pekerjaan dari utas UI saat tombol diklik. Ini mengharuskan server HTTP berkomunikasi dengan layanan lain untuk menjalankan tugas daripada menjalankannya dalam penangan permintaan. Ini bukan implementasi yang dangkal, ini adalah perubahan arah.


Beberapa contoh bagaimana skema URI akan digunakan

Menjalankan satu tugas dan melacak kemajuan:

  • POST /tasks dengan tugas untuk dieksekusi
    • GET /tasks/task/[id]hingga objek respons completedmemiliki nilai positif sambil menunjukkan status / kemajuan saat ini

Menjalankan satu tugas dan menunggu penyelesaiannya:

  • POST /tasks dengan tugas untuk dieksekusi
    • GET /tasks/task/[id]?awaitCompletion=truehingga completedmemiliki nilai positif (kemungkinan memiliki batas waktu, itulah sebabnya ini harus diulang)

Menjalankan grup tugas dan melacak kemajuan:

  • POST /tasks/groups dengan kelompok tugas untuk dieksekusi
    • GET /tasks/groups/group/[groupId]sampai completedproperti objek respons memiliki nilai, menunjukkan status tugas individu (3 tugas diselesaikan dari 5, misalnya)

Meminta eksekusi untuk grup tugas dan menunggu penyelesaiannya:

  • POST /tasks/groups dengan kelompok tugas untuk dieksekusi
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true sampai merespons dengan hasil yang menunjukkan penyelesaian (kemungkinan memiliki batas waktu, itulah sebabnya mengapa harus diulang)
Ben Barkay
sumber
Saya pikir berbicara tentang apa yang masuk akal secara semantik adalah cara yang tepat untuk mendekati ini. Terima kasih!
Anders
2
Saya akan mengusulkan jawaban ini jika belum ada di sana. Tidak mungkin membuat banyak permintaan dalam satu permintaan HTTP. Di sisi lain, sangat mungkin untuk membuat permintaan HTTP tunggal yang mengatakan "lakukan tindakan berikut, dan beri tahu saya apa hasilnya". Dan inilah yang terjadi di sini.
Martin Kochanski
Saya akan menerima jawaban ini walaupun mereka jauh dari suara terbanyak. Sementara jawaban lain juga bagus, saya pikir ini adalah satu-satunya alasan tentang semantik HTTP.
Anders
87

Pilihan saya adalah membagi tugas-tugas ini menjadi permintaan terpisah. Namun, jika terlalu banyak pulang pergi yang memprihatinkan, saya menemukan kode respons HTTP 207 - Multi-Status

Salin / tempel dari tautan ini:

Respons Multi-Status menyampaikan informasi tentang banyak sumber daya dalam situasi di mana beberapa kode status mungkin sesuai. Badan respons Multi-Status default adalah entitas HTTP teks / xml atau aplikasi / xml dengan elemen root 'multistatus'. Elemen lebih lanjut berisi 200, 300, 400, dan 500 kode status seri yang dihasilkan selama pemanggilan metode. 100 kode status seri TIDAK HARUS direkam dalam elemen XML 'respons'.

Meskipun '207' digunakan sebagai kode status respons keseluruhan, penerima perlu berkonsultasi dengan isi badan respons multistatus untuk informasi lebih lanjut tentang keberhasilan atau kegagalan eksekusi metode. Respons MUNGKIN digunakan dalam kesuksesan, keberhasilan sebagian dan juga dalam situasi kegagalan.

neilsimp1
sumber
22
207memang terlihat seperti yang diinginkan OP, tapi saya benar-benar ingin menekankan bahwa mungkin ini ide yang buruk untuk memiliki pendekatan multi-permintaan-dalam-satu ini. Jika yang menjadi perhatian adalah kinerja maka Anda harus merancang sistem cloud-scalable secara horizontal (yang merupakan sesuatu yang sangat bagus untuk sistem berbasis HTTP)
Reinstate Monica
44
@ DavidVrinberg Saya tidak bisa tidak setuju lagi. Jika tindakan individu itu murah, maka overhead penanganan permintaan bisa jauh lebih mahal daripada tindakan itu sendiri. Saran Anda dapat mengarah ke skenario di mana memperbarui beberapa baris dalam database dilakukan menggunakan transaksi terpisah per baris karena setiap baris dikirim sebagai permintaan terpisah. Ini tidak hanya sangat tidak efisien, tetapi juga berarti tidak mungkin untuk memperbarui beberapa baris secara atomis jika diperlukan. Penskalaan horisontal penting tetapi itu bukan pengganti untuk desain yang efisien.
kasperd
4
Dikatakan dengan baik dan menunjukkan masalah khas implementasi REST API yang dilakukan oleh orang-orang yang tidak tahu terhadap kenyataan kebutuhan bisnis seperti kinerja dan / atau atomisitas. Itulah sebabnya, misalnya, spesifikasi OData REST memiliki format batch untuk multi operasi dalam satu panggilan - ada kebutuhan nyata untuk itu.
TomTom
8
@ TomTom, OP tidak menginginkan atomicity. Itu akan menjadi hal yang jauh lebih mudah untuk dirancang, karena hanya ada satu status operasi atom. Selain itu, spesifikasi HTTP tidak memungkinkan operasi batching untuk kinerja, melalui HTTP / 2 multiplexing (tentu saja, dukungan HTTP / 2 adalah masalah lain, tetapi spec memungkinkan untuk itu).
Paul Draper
2
@ David Setelah bekerja pada beberapa masalah HPC di masa lalu, dalam pengalaman saya biaya pengiriman byte tunggal hampir sama dengan mengirim seribu (media transfer yang berbeda tentu memiliki overhead yang berbeda, tetapi jarang lebih baik daripada ini). Jadi, jika kinerja menjadi perhatian, saya tidak melihat bagaimana mengirim beberapa permintaan tidak akan memiliki overhead yang besar. Sekarang jika Anda dapat mengalikan beberapa permintaan melalui koneksi yang sama masalah ini akan hilang, tetapi seperti yang saya pahami, itu hanya opsi dengan HTTP / 2 dan dukungan untuknya agak terbatas. Atau apakah saya melewatkan sesuatu?
Voo
24

Meskipun multi-status adalah opsi, saya akan mengembalikan 200 (Semua baik-baik saja) jika semua permintaan berhasil dan kesalahan (500 atau mungkin 207) sebaliknya.

Kasing standar biasanya harus 200 - semuanya berfungsi. Dan klien hanya perlu memeriksa itu. Dan hanya jika kasus kesalahan terjadi Anda dapat mengembalikan 500 (atau 207). Saya pikir 207 adalah pilihan yang valid dalam kasus setidaknya satu kesalahan, tetapi jika Anda melihat seluruh paket sebagai satu transaksi Anda juga dapat mengirim 500. - Klien akan ingin menafsirkan pesan kesalahan dengan cara baik.

Mengapa tidak selalu mengirim 207? - Karena case standar harus mudah dan standar. Sementara kasus luar biasa bisa luar biasa. Seorang klien hanya harus membaca badan respon dan melakukan keputusan rumit lebih lanjut, jika situasi luar biasa menjaminnya.

Falco
sumber
6
Saya tidak begitu setuju. Jika subrequest 1 dan 3 berhasil, Anda mendapatkan sumber daya gabungan dan tetap harus memeriksa respons gabungan. Anda hanya perlu mempertimbangkan satu kasus lagi. Jika respons = 200 atau subresponse 1 = 200 maka permintaan 1 berhasil. Jika respons = 200 atau subresponse 2 = 200 maka permintaan 2 berhasil dan seterusnya, alih-alih hanya menguji sub respons.
gnasher729
1
@ gnasher729 sangat tergantung pada aplikasinya. Saya membayangkan tindakan yang digerakkan pengguna, yang hanya akan mengalir ke langkah berikutnya dengan (semuanya baik-baik saja) ketika semua permintaan berhasil. - Jika terjadi kesalahan (kondisi global <= 200) maka Anda harus menampilkan kesalahan terperinci dan mengubah alur kerja, dan hanya perlu pemeriksaan tunggal untuk setiap subrequest, karena Anda berada dalam fungsi "handleMixedState" dan bukan fungsi "handleAllOk" .
Falco
Itu benar-benar tergantung pada artinya. Sebagai contoh, saya memiliki titik akhir yang mengontrol strategi perdagangan. Anda dapat "memulai" daftar pengidentifikasi dalam satu kali proses. Kembali 200 berarti operasi (prosesnya) berhasil - tetapi tidak semua dapat mulai berhasil. Yang, btw., Bahkan tidak dapat dilihat pada hasil langsung (yang akan dimulai) karena startup dapat memakan waktu beberapa detik. Semantik dalam panggilan multi-operasi tergantung pada skenario.
TomTom
Saya juga kemungkinan besar akan mengirim 500 jika ada Masalah umum (misalnya Basis data turun) sehingga server bahkan tidak mencoba permintaan individu, tetapi hanya dapat mengembalikan kesalahan umum. - Karena ada 3 hasil berbeda untuk pengguna 1. semua OK, 2. masalah umum, tidak ada yang berhasil, 3. Beberapa permintaan gagal. -> Yang biasanya akan mengarah ke aliran program yang sama sekali berbeda.
Falco
1
Oke, jadi satu pendekatan adalah: 207 = status individu untuk setiap permintaan. Hal lain: Status yang dikembalikan berlaku untuk setiap permintaan. Masuk akal untuk 200, 401, ≥ 500.
gnasher729
13

Salah satu opsi adalah untuk selalu mengembalikan kode status 200 dan kemudian mengembalikan kesalahan spesifik di badan dokumen JSON Anda. Ini adalah persis bagaimana beberapa API dirancang (mereka selalu mengembalikan kode status 200 dan mengirimkan kesalahan dalam tubuh). Untuk detail lebih lanjut tentang pendekatan yang berbeda, lihat http://archive.oreilly.com/pub/post/restful_error_handling.html

BigONotation
sumber
2
Dalam hal ini, saya suka ide untuk menggunakan 200untuk menunjukkan semuanya baik-baik, permintaan diterima dan valid , dan kemudian menggunakan JSON untuk memberikan rincian tentang apa yang terjadi dalam permintaan itu (yaitu hasil dari transaksi).
rickcnagy
4

Saya pikir neilsimp1 benar, tetapi saya akan merekomendasikan desain ulang data yang dikirim sedemikian rupa sehingga Anda dapat mengirim 206 - Accepteddan memproses data nanti. Mungkin dengan panggilan balik.

Masalah dengan mencoba mengirim beberapa tindakan dalam satu permintaan adalah fakta bahwa setiap tindakan harus memiliki "status" sendiri

Melihat mengimpor CSV (saya tidak tahu benar-benar tentang OP tapi itu versi sederhana). POST CSV dan dapatkan 206. Kemudian CSV dapat diimpor dan Anda bisa mendapatkan status impor dengan GET (200) terhadap URL yang menunjukkan kesalahan per baris.

POST /imports/ -> 206
GET  /imports/1 -> 200
GET  /imports/1/errors -> 200 -> Has a list of errors

Pola yang sama ini dapat diterapkan pada banyak operasi batch

POST /operations/ -> 206
GET  /operations/1 -> 200
GET  /operations/1/errors -> 200 - > Has a list of errors.

Kode yang menangani POST hanya perlu memverifikasi bahwa format data operasi valid. Kemudian di beberapa waktu kemudian operasi dapat dijalankan. Di pekerja tanah belakang, sehingga Anda dapat skala lebih mudah, misalnya. Kemudian Anda dapat memeriksa status operasi kapan pun Anda mau. Anda dapat menggunakan polling atau menelepon balik, atau stream atau apa pun untuk mengatasi kebutuhan untuk mengetahui kapan satu set operasi selesai.

kapas
sumber
2

Sudah banyak jawaban bagus di sini, tetapi satu aspek tidak ada:

Apa kontrak yang diharapkan klien Anda?

Kode pengembalian HTTP harus menunjukkan setidaknya perbedaan keberhasilan / kegagalan dan dengan demikian memainkan peran "pengecualian orang miskin". Kemudian 200 berarti "kontrak sepenuhnya terpenuhi", dan 4xx atau 5xx menunjukkan kegagalan untuk memenuhi.

Secara naif, saya berharap kontrak permintaan beberapa tindakan Anda adalah "melakukan semua tugas saya", dan jika salah satu dari mereka gagal, maka permintaan itu tidak (sepenuhnya) berhasil. Biasanya, sebagai klien saya akan mengerti 200 sebagai "semuanya baik-baik saja", dan kode dari keluarga 400 dan 500 memaksa saya untuk memikirkan konsekuensi dari kegagalan (sebagian). Jadi, gunakan 200 untuk "semua tugas dilakukan" dan 500 ditambah respons deskriptif jika terjadi kegagalan parsial.

Kontrak hipotetis yang berbeda mungkin "coba lakukan semua tindakan". Maka itu sepenuhnya sesuai dengan kontrak jika (beberapa) tindakan gagal. Jadi, Anda akan selalu mengembalikan 200 plus dokumen hasil di mana Anda menemukan informasi keberhasilan / kegagalan untuk tugas individu.

Jadi, apa kontrak yang ingin Anda ikuti? Keduanya valid, tetapi yang pertama (200 hanya jika semuanya dilakukan) lebih intuitif bagi saya, dan lebih baik sejalan dengan pola perangkat lunak yang khas. Dan untuk sebagian besar (mudah-mudahan) kasus di mana layanan menyelesaikan semua tugas, itu mudah bagi klien untuk mendeteksi kasus itu.

Aspek penting terakhir: Bagaimana Anda mengomunikasikan keputusan kontrak Anda kepada klien Anda? Misalnya di Jawa, saya akan menggunakan nama metode seperti "doAll ()" atau "tryToDoAll ()". Dalam HTTP, Anda dapat memberi nama URL titik akhir sesuai, berharap bahwa pengembang klien Anda melihat, membaca, dan memahami penamaan (saya tidak akan bertaruh untuk itu). Satu lagi alasan untuk memilih kontrak yang paling tidak mengejutkan.

Ralf Kleberhoff
sumber
0

Menjawab:

Cukup gunakan satu permintaan per tindakan, dan terima overhead tambahan.

Kode status menjelaskan status satu operasi. Karena itu, masuk akal untuk memiliki satu operasi per permintaan.

Beberapa operasi independen melanggar prinsip yang menjadi dasar model respons-permintaan dan kode status. Anda melawan alam.

HTTP / 1.1 dan HTTP / 2 telah membuat overhead permintaan HTTP jauh lebih rendah. Saya memperkirakan ada sangat sedikit situasi di mana permintaan independen batching disarankan.


Yang mengatakan,

(1) Anda dapat membuat banyak modifikasi dengan permintaan PATCH ( RFC 5789 ). Namun, ini mensyaratkan bahwa perubahan untuk tidak independen; mereka diterapkan secara atom (semua atau tidak sama sekali).

(2) Orang lain telah menunjukkan 207 kode Multi-Status. Namun, ini hanya ditentukan untuk WebDAV ( RFC 4918 ), perpanjangan dari HTTP.

Kode status 207 (Multi-Status) memberikan status untuk beberapa operasi independen (lihat Bagian 13 untuk informasi lebih lanjut).

...

Respons Multi-Status menyampaikan informasi tentang banyak sumber daya dalam situasi di mana beberapa kode status mungkin sesuai. Elemen 'multistatus' root [XML] menampung nol atau lebih elemen 'respons' dalam urutan apa pun, masing-masing dengan informasi tentang sumber daya individual.

Respons 207 WebDAV XML akan seaneh bebek di API non-WebDAV. Jangan lakukan ini.

Paul Draper
sumber
1
Anda pada dasarnya menyatakan bahwa @Anders memiliki masalah XY . Anda mungkin benar, tetapi sayangnya, itu berarti bahwa Anda belum benar-benar menjawab pertanyaan yang dia tanyakan (kode status apa yang digunakan untuk permintaan multi-tindakan).
Azuaron
2
@ Azuaron, ikat pinggang apa yang paling cocok untuk mengalahkan anak-anak? Saya pikir "T / A" adalah jawaban yang diijinkan. Selain itu, Andres memasukkan banyak permintaan dalam daftar gagasannya. Saya dengan sepenuh hati mendukung opsi itu.
Paul Draper
Entah bagaimana aku merindukan bahwa dia mendaftar itu. Kalau begitu, saya tegaskan itu pertanyaan konyol, Yang Mulia!
Azuaron
1
@ Azuaron Saya benar-benar berpikir ini adalah jawaban yang valid. Jika saya melakukan semuanya salah, saya ingin seseorang mengatakannya, dan tidak memberi saya petunjuk tentang cara terbaik untuk mengusir tebing.
Anders
1
Tidak ada yang melarang mengirim JSON dalam respons 207, selama header Tipe Konten diatur dengan benar dan cocok dengan apa yang diminta klien (Terima header).
dolmen
0

Jika Anda benar-benar perlu memiliki beberapa tindakan dalam satu permintaan, mengapa tidak membungkus semua tindakan dalam transaksi di backend? Dengan begitu mereka semua berhasil atau gagal.

Sebagai klien yang menggunakan API, saya dapat menangani kesuksesan atau kegagalan total pada panggilan API. Kesuksesan parsial sulit untuk dihadapi, karena saya harus menangani semua kemungkinan kondisi yang dihasilkan.

Dekan
sumber
2
Saya berasumsi jika permintaan itu bersifat atomik, dia tidak akan memposting pertanyaan ini.
Andy
@Andy Mungkin, tetapi Anda tidak dapat menganggap dia menganggap semua implikasi dari desain seperti itu.
Dean
Permintaan tidak boleh berupa atom - mis. Jika # 2 gagal, perubahan yang dilakukan oleh # 1 harus tetap ada. Jadi, membungkus segala sesuatu dalam satu transaksi bukanlah pilihan.
Anders