REST API - pemrosesan file (yaitu gambar) - praktik terbaik

198

Kami sedang mengembangkan server dengan REST API, yang menerima dan merespons dengan JSON. Masalahnya adalah, jika Anda perlu mengunggah gambar dari klien ke server.

Catatan: dan juga saya berbicara tentang kasus penggunaan di mana entitas (pengguna) dapat memiliki beberapa file (carPhoto, licensePhoto) dan juga memiliki properti lainnya (nama, email ...), tetapi ketika Anda membuat pengguna baru, Anda tidak dapat mengirim gambar-gambar ini, mereka ditambahkan setelah proses pendaftaran.


Solusi yang saya sadari, tetapi masing-masing memiliki beberapa kekurangan

1. Gunakan multipart / form-data alih-alih JSON

Bagus : Permintaan POST dan PUT senyap mungkin, mereka dapat berisi input teks bersama dengan file.

kontra : Ini bukan JSON lagi, yang jauh lebih mudah untuk menguji, men-debug dll. dibandingkan dengan multipart / form-data

2. Izinkan untuk memperbarui file yang terpisah

Permintaan POST untuk membuat pengguna baru tidak memungkinkan untuk menambahkan gambar (yang ok dalam kasus penggunaan kami seperti yang saya katakan di awal), mengunggah gambar dilakukan dengan permintaan PUT sebagai multipart / formulir-data ke misalnya / pengguna / 4 / carPhoto

bagus : Semuanya (kecuali mengunggah file itu sendiri) tetap di JSON, mudah untuk menguji dan men-debug (Anda dapat mencatat permintaan JSON lengkap tanpa takut panjangnya)

kontra : Ini tidak intuitif, Anda tidak bisa POST atau PUT semua variabel entitas sekaligus dan alamat ini /users/4/carPhotodapat dianggap lebih sebagai koleksi (kasus penggunaan standar untuk REST API terlihat seperti ini /users/4/shipments). Biasanya Anda tidak dapat (dan tidak mau) DAPATKAN / PUT setiap variabel entitas, misalnya pengguna / 4 / nama. Anda bisa mendapatkan nama dengan GET dan mengubahnya dengan PUT di pengguna / 4. Jika ada sesuatu setelah id, biasanya koleksi lain, seperti pengguna / 4 / ulasan

3. Gunakan Base64

Kirim sebagai JSON tetapi menyandikan file dengan Base64.

Bagus : Sama seperti solusi pertama, ini adalah layanan yang tenang.

Kontra : Sekali lagi, pengujian dan debugging jauh lebih buruk (tubuh dapat memiliki megabita data), ada peningkatan ukuran dan juga dalam waktu pemrosesan di kedua - klien dan server


Saya benar-benar ingin menggunakan solusi no. 2, tetapi memiliki kontra ... Adakah yang bisa memberi saya wawasan yang lebih baik tentang solusi "apa yang terbaik"?

Tujuan saya adalah memiliki layanan tenang dengan standar sebanyak mungkin, sementara saya ingin membuatnya sesederhana mungkin.

libik
sumber
Anda mungkin juga menemukan ini berguna: stackoverflow.com/questions/4083702/…
Markon
5
Saya tahu topik ini sudah lama tetapi kami menghadapi masalah ini baru-baru ini. Pendekatan terbaik yang kami miliki mirip dengan nomor Anda 2. Kami mengunggah file langsung ke API dan kemudian melampirkan file-file ini dalam model. Dengan skenario ini Anda dapat membuat unggah gambar sebelum, sesudah atau pada halaman yang sama dengan formulir, tidak terlalu penting. Diskusi yang bagus!
Tiago Matos
2
@TiagoMatos - ya, tepatnya, saya jelaskan dalam satu jawaban yang baru-baru ini saya terima
libik
6
Terima kasih telah mengajukan pertanyaan ini.
Zuhayer Tahir
1
"juga alamat / pengguna / 4 / carPhoto ini dapat dianggap lebih sebagai koleksi" - tidak, itu tidak terlihat seperti koleksi dan tidak akan dianggap sebagai koleksi. Benar-benar baik untuk memiliki hubungan dengan sumber daya yang bukan koleksi tetapi sumber daya tunggal.
B12Toaster

Jawaban:

156

OP di sini (saya menjawab pertanyaan ini setelah dua tahun, posting yang dibuat oleh Daniel Cerecedo tidak buruk pada suatu waktu, tetapi layanan web berkembang sangat cepat)

Setelah tiga tahun pengembangan perangkat lunak penuh waktu (dengan fokus juga pada arsitektur perangkat lunak, manajemen proyek, dan arsitektur layanan mikro), saya pasti memilih cara kedua (tetapi dengan satu titik akhir umum) sebagai yang terbaik.

Jika Anda memiliki titik akhir khusus untuk gambar, itu memberi Anda lebih banyak kekuatan untuk menangani gambar-gambar itu.

Kami memiliki REST API (Node.js) yang sama untuk keduanya - aplikasi seluler (iOS / android) dan frontend (menggunakan Bereaksi). Ini adalah 2017, karena itu Anda tidak ingin menyimpan gambar secara lokal, Anda ingin mengunggahnya ke beberapa penyimpanan cloud (Google cloud, s3, cloudinary, ...), oleh karena itu Anda ingin penanganan secara umum.

Aliran khas kami adalah, bahwa segera setelah Anda memilih gambar, itu mulai mengunggah di latar (biasanya POST pada / gambar endpoint), mengembalikan Anda ID setelah mengunggah. Ini sangat user-friendly, karena pengguna memilih gambar dan kemudian biasanya melanjutkan dengan beberapa bidang lain (yaitu alamat, nama, ...), oleh karena itu ketika ia menekan tombol "kirim", gambar biasanya sudah diunggah. Dia tidak menunggu dan menonton layar yang mengatakan "mengunggah ...".

Hal yang sama berlaku untuk mendapatkan gambar. Terutama berkat telepon seluler dan data seluler terbatas, Anda tidak ingin mengirim gambar asli, Anda ingin mengirim gambar yang diubah ukurannya, sehingga mereka tidak mengambil banyak bandwidth (dan untuk membuat aplikasi seluler Anda lebih cepat, Anda sering tidak ingin untuk mengubah ukurannya sama sekali, Anda ingin gambar yang pas dengan tampilan Anda). Untuk alasan ini, aplikasi yang bagus menggunakan sesuatu seperti cloudinary (atau kami memiliki server gambar kami sendiri untuk mengubah ukuran).

Juga, jika data tidak bersifat pribadi, maka Anda mengirim kembali ke URL aplikasi / frontend saja dan mengunduhnya dari penyimpanan cloud secara langsung, yang merupakan penghematan besar bandwidth dan waktu pemrosesan untuk server Anda. Di aplikasi kami yang lebih besar, ada banyak terabyte yang diunduh setiap bulan, Anda tidak ingin mengatasinya secara langsung pada setiap server REST API Anda, yang difokuskan pada operasi CRUD. Anda ingin menangani itu di satu tempat (Imageserver kami, yang memiliki caching dll.) Atau membiarkan layanan cloud menangani semua itu.


Cons: Satu-satunya "kontra" yang harus Anda pikirkan adalah "tidak ditugaskan gambar". Pengguna memilih gambar dan melanjutkan dengan mengisi bidang lain, tetapi kemudian dia berkata "nah" dan mematikan aplikasi atau tab, tetapi sementara itu Anda berhasil mengunggah gambar. Ini berarti Anda telah mengunggah gambar yang tidak ditugaskan di mana pun.

Ada beberapa cara untuk menangani ini. Yang paling mudah adalah "Saya tidak peduli", yang relevan, jika ini tidak sering terjadi atau Anda bahkan ingin menyimpan setiap pengguna gambar yang mengirimi Anda (untuk alasan apa pun) dan Anda tidak menginginkannya. penghapusan.

Yang lain juga mudah - Anda memiliki CRON dan yaitu setiap minggu dan Anda menghapus semua gambar yang belum ditetapkan lebih dari satu minggu.

libik
sumber
Apa yang akan terjadi jika [segera setelah Anda memilih gambar, itu mulai mengunggah di latar belakang (biasanya POST pada / gambar endpoint), mengembalikan Anda ID setelah mengunggah] ketika permintaan gagal karena koneksi internet? Apakah Anda akan meminta pengguna saat mereka melanjutkan dengan beberapa bidang lain (yaitu alamat, nama, ...)? Saya yakin Anda masih akan menunggu sampai pengguna menekan tombol "kirim" dan coba lagi permintaan Anda membuat mereka menunggu sambil menonton layar yang mengatakan "uploadiing ...".
Adromil Balais
5
@AdromilBalais - RESTful API adalah stateless, oleh karena itu ia tidak melakukan apa-apa (Server tidak melacak status konsumen). Konsumen layanan (yaitu halaman web atau perangkat seluler) bertanggung jawab untuk menangani permintaan yang gagal, oleh karena itu konsumen harus memutuskan apakah panggilan itu segera meminta permintaan yang sama setelah yang ini gagal atau apa yang harus dilakukan (mis. Tunjukkan "Unggahan gambar gagal - ingin mencoba lagi ")
libik
2
Jawaban yang sangat informatif dan mencerahkan. Terimakasih telah menjawab.
Zuhayer Tahir
Ini tidak benar-benar menyelesaikan masalah awal. Ini hanya mengatakan "menggunakan layanan cloud"
Martin Muzatko
3
@ MartinMuzatko - ya, ia memilih opsi kedua dan memberi tahu Anda bagaimana Anda harus menggunakannya dan mengapa. Jika maksud Anda "tapi ini bukan opsi sempurna yang memungkinkan Anda mengirim semuanya dalam satu permintaan dan tanpa implikasi" - ya, sayangnya tidak ada solusi seperti itu.
Libib
105

Ada beberapa keputusan yang harus diambil :

  1. Yang pertama tentang jalur sumber daya :

    • Model gambar sebagai sumber daya sendiri:

      • Nested in user (/ user /: id / image): hubungan antara pengguna dan gambar dibuat secara implisit

      • Di jalur root (/ gambar):

        • Klien dianggap bertanggung jawab untuk membangun hubungan antara gambar dan pengguna, atau;

        • Jika konteks keamanan disediakan dengan permintaan POST yang digunakan untuk membuat gambar, server dapat secara implisit membangun hubungan antara pengguna yang diautentikasi dan gambar.

    • Sematkan gambar sebagai bagian dari pengguna

  2. Keputusan kedua adalah tentang bagaimana merepresentasikan sumber gambar :

    • Sebagai Basis 64 disandikan payload JSON
    • Sebagai muatan multi bagian

Ini akan menjadi jalur keputusan saya:

  • Saya biasanya lebih menyukai desain daripada kinerja kecuali ada alasan kuat untuk itu. Itu membuat sistem lebih dapat dipelihara dan lebih mudah dipahami oleh integrator.
  • Jadi pemikiran pertama saya adalah pergi untuk representasi Base64 dari sumber daya gambar karena itu memungkinkan Anda menjaga semuanya JSON. Jika Anda memilih opsi ini, Anda dapat memodelkan jalur sumber daya sesuka Anda.
    • Jika hubungan antara pengguna dan gambar adalah 1 banding 1, saya lebih suka memodelkan gambar sebagai atribut khusus jika kedua set data diperbarui pada saat yang sama. Dalam kasus lain, Anda dapat dengan bebas memilih untuk memodelkan gambar baik sebagai atribut, memperbaruinya melalui PUT atau PATCH, atau sebagai sumber daya yang terpisah.
  • Jika Anda memilih muatan multi bagian, saya merasa harus memodelkan gambar sebagai sumber daya, jadi sumber daya lain, dalam kasus kami, sumber daya pengguna, tidak terpengaruh oleh keputusan menggunakan representasi biner untuk gambar.

Lalu muncul pertanyaan: Apakah ada dampak kinerja tentang memilih base64 vs multipart? . Kita dapat berpikir bahwa bertukar data dalam format multi bagian harus lebih efisien. Tetapi artikel ini menunjukkan betapa sedikit perbedaan kedua representasi dalam hal ukuran.

Base64 pilihan saya:

  • Keputusan desain yang konsisten
  • Dampak kinerja yang dapat diabaikan
  • Saat browser memahami URI data (gambar yang disandikan base64), tidak perlu mengubah ini jika klien adalah browser
  • Saya tidak akan memberikan suara apakah memilikinya sebagai atribut atau sumber daya mandiri, itu tergantung pada domain masalah Anda (yang saya tidak tahu) dan preferensi pribadi Anda.
Daniel Cerecedo
sumber
3
Bisakah kita menyandikan data menggunakan protokol serialisasi lain seperti protobuf dll? Pada dasarnya saya mencoba untuk memahami jika ada cara lain yang lebih sederhana untuk mengatasi peningkatan ukuran & waktu pemrosesan yang datang dengan pengkodean base64.
Andy Dufresne
1
Jawaban yang sangat menarik. terima kasih untuk pendekatan langkah demi langkah. Itu membuat saya lebih memahami poin Anda.
Zuhayer Tahir
13

Solusi kedua Anda mungkin yang paling benar. Anda harus menggunakan spesifikasi HTTP dan meniru jenisnya seperti yang dimaksudkan dan mengunggah file melalui multipart/form-data. Sejauh menangani hubungan, saya akan menggunakan proses ini (mengingat saya tidak tahu tentang asumsi atau desain sistem Anda):

  1. POSTuntuk /usersmembuat entitas pengguna.
  2. POSTgambar ke /images, pastikan untuk mengembalikan Locationheader ke tempat gambar dapat diambil per spec HTTP.
  3. PATCHke /users/carPhotodan menetapkan ID dari foto yang diberikan di Locationheader langkah 2.
mmcclannahan
sumber
1
Saya tidak memiliki kontrol langsung "bagaimana klien akan menggunakan API" ... Masalahnya adalah gambar "mati" yang tidak ditambal ke beberapa sumber daya ...
libik
4
Biasanya ketika Anda memilih opsi kedua, lebih disukai untuk mengunggah terlebih dahulu elemen media dan mengembalikan pengenal media ke klien, maka klien dapat mengirim data entitas termasuk pengidentifikasi media, pendekatan ini menghindari entitas yang rusak atau info mismatch.
Kellerman Rivero
2

Tidak ada solusi mudah. Setiap cara memiliki pro dan kontra mereka. Tapi cara kanonik adalah menggunakan opsi pertama: multipart/form-data. Seperti yang dikatakan panduan rekomendasi W3

Jenis konten "multipart / formulir-data" harus digunakan untuk mengirimkan formulir yang berisi file, data non-ASCII, dan data biner.

Kami tidak mengirimkan formulir, sungguh, tetapi prinsip implisit masih berlaku. Menggunakan base64 sebagai representasi biner, salah karena Anda menggunakan alat yang salah untuk mencapai tujuan Anda, di sisi lain, opsi kedua memaksa klien API Anda untuk melakukan lebih banyak pekerjaan agar dapat menggunakan layanan API Anda. Anda harus melakukan kerja keras di sisi server untuk memasok API yang mudah dikonsumsi. Opsi pertama tidak mudah untuk di-debug, tetapi ketika Anda melakukannya, mungkin tidak pernah berubah.

Menggunakan multipart/form-dataAnda terpaku pada filosofi REST / http. Anda dapat melihat jawaban untuk pertanyaan serupa di sini .

Pilihan lain jika menggabungkan alternatif, Anda dapat menggunakan multipart / form-data tetapi alih-alih mengirim setiap nilai secara terpisah, Anda dapat mengirim nilai yang bernama payload dengan muatan json di dalamnya. (Saya mencoba pendekatan ini menggunakan ASP.NET WebAPI 2 dan berfungsi dengan baik).

Kellerman Rivero
sumber
2
Panduan rekomendasi W3 itu tidak relevan di sini, karena itu dalam konteks spesifikasi HTML 4.
Johann
1
Sangat benar .... "non-ASCII-data" membutuhkan multipart? Di abad kedua puluh satu? Di dunia UTF-8? Tentu saja itu adalah rekomendasi konyol untuk hari ini. Saya bahkan terkejut bahwa ada dalam HTML 4 hari, tetapi kadang-kadang dunia infrastruktur Internet bergerak sangat lambat.
Ray Toal