API REST - xử lý tệp (tức là hình ảnh) - thực tiễn tốt nhất


194

Chúng tôi đang phát triển máy chủ với API REST, chấp nhận và trả lời bằng JSON. Vấn đề là, nếu bạn cần tải hình ảnh từ máy khách lên máy chủ.

Lưu ý: và tôi cũng đang nói về trường hợp sử dụng trong đó thực thể (người dùng) có thể có nhiều tệp (carPhoto, LicensePhoto) và cũng có các thuộc tính khác (tên, email ...), nhưng khi bạn tạo người dùng mới, bạn không Không gửi những hình ảnh này, chúng được thêm vào sau quá trình đăng ký.


Các giải pháp tôi biết, nhưng mỗi giải pháp đều có một số sai sót

1. Sử dụng nhiều dữ liệu / biểu mẫu dữ liệu thay vì JSON

tốt : Các yêu cầu POST và PUT càng RESTful càng tốt, chúng có thể chứa các kiểu nhập văn bản cùng với tệp.

Nhược điểm : Nó không phải là JSON nữa, dễ kiểm tra, gỡ lỗi hơn, v.v. so với đa dữ liệu / biểu mẫu dữ liệu

2. Cho phép cập nhật các tệp riêng biệt

Yêu cầu POST để tạo người dùng mới không cho phép thêm hình ảnh (điều này cũng ổn trong trường hợp sử dụng của chúng tôi như tôi đã nói lúc đầu), tải lên hình ảnh được thực hiện bởi yêu cầu PUT dưới dạng nhiều dữ liệu / biểu mẫu cho ví dụ / users / 4 / carPhoto

tốt : Mọi thứ (ngoại trừ tệp tự tải lên) vẫn còn trong JSON, rất dễ kiểm tra và gỡ lỗi (bạn có thể đăng nhập hoàn thành các yêu cầu JSON mà không sợ độ dài của chúng)

Nhược điểm : Nó không trực quan, bạn không thể POST hoặc PUT tất cả các biến của thực thể cùng một lúc và địa chỉ này /users/4/carPhotocó thể được coi là một bộ sưu tập (trường hợp sử dụng tiêu chuẩn cho API REST trông như thế này /users/4/shipments). Thông thường, bạn không thể (và không muốn) NHẬN / PUT từng biến thực thể, ví dụ người dùng / 4 / tên. Bạn có thể lấy tên bằng GET và thay đổi nó bằng PUT tại người dùng / 4. Nếu có một cái gì đó sau id, nó thường là một bộ sưu tập khác, như người dùng / 4 / đánh giá

3. Sử dụng Base64

Gửi dưới dạng JSON nhưng mã hóa tệp bằng Base64.

tốt : Giống như giải pháp đầu tiên, đó là dịch vụ RESTful nhất có thể.

Nhược điểm : Một lần nữa, kiểm tra và gỡ lỗi tồi tệ hơn rất nhiều (cơ thể có thể có megabyte dữ liệu), có sự gia tăng kích thước và cả thời gian xử lý ở cả - máy khách và máy chủ


Tôi thực sự muốn sử dụng giải pháp không. 2, nhưng nó có nhược điểm của nó ... Bất cứ ai cũng có thể cho tôi cái nhìn sâu sắc hơn về giải pháp "cái gì là tốt nhất"?

Mục tiêu của tôi là có các dịch vụ RESTful với càng nhiều tiêu chuẩn càng tốt, trong khi tôi muốn giữ nó đơn giản nhất có thể.


Bạn cũng có thể thấy điều này hữu ích: stackoverflow.com/questions
4083702 / Mark

5
Tôi biết chủ đề này đã cũ nhưng chúng tôi đã đối mặt với vấn đề này gần đây. Cách tiếp cận tốt nhất mà chúng tôi có được tương tự như số 2. Của bạn, chúng tôi tải các tệp trực tiếp lên API và sau đó đính kèm các tệp này trong mô hình. Với kịch bản này, bạn có thể tạo hình ảnh tải lên trước, sau hoặc tại cùng một trang với biểu mẫu, không thực sự quan trọng. Thảo luận tốt!
Tiago Matos

2
@TiagoMatos - vâng, chính xác, tôi đã mô tả nó trong một câu trả lời mà tôi mới chấp nhận
libik

6
Cảm ơn đã hỏi câu hỏi này.
Zuhayer Tahir

1
"Ngoài ra địa chỉ này / người dùng / 4 / carPhoto có thể được coi là một bộ sưu tập" - không, nó không giống như một bộ sưu tập và không nhất thiết phải được coi là một bộ sưu tập. Hoàn toàn ổn khi có mối quan hệ với một tài nguyên không phải là một bộ sưu tập mà là một tài nguyên duy nhất.
B12Toaster

Câu trả lời:


152

OP ở đây (Tôi đang trả lời câu hỏi này sau hai năm, bài đăng được thực hiện bởi Daniel Cerecedo tại thời điểm đó không tệ, nhưng các dịch vụ web đang phát triển rất nhanh)

Sau ba năm phát triển phần mềm toàn thời gian (tập trung vào kiến ​​trúc phần mềm, quản lý dự án và kiến ​​trúc microservice), tôi chắc chắn chọn cách thứ hai (nhưng với một điểm cuối chung) là cách tốt nhất.

Nếu bạn có một điểm cuối đặc biệt cho hình ảnh, nó mang lại cho bạn nhiều sức mạnh hơn trong việc xử lý những hình ảnh đó.

Chúng tôi có cùng API REST (Node.js) cho cả hai - ứng dụng di động (iOS / android) và frontend (sử dụng React). Đây là năm 2017, do đó bạn không muốn lưu trữ hình ảnh cục bộ, bạn muốn tải chúng lên một số lưu trữ đám mây (Google cloud, s3, cloudinary, ...), do đó bạn muốn xử lý chung chúng.

Luồng điển hình của chúng tôi là, ngay khi bạn chọn một hình ảnh, nó sẽ bắt đầu tải lên trên nền (thường là POST trên / điểm cuối hình ảnh), trả lại cho bạn ID sau khi tải lên. Điều này thực sự thân thiện với người dùng, vì người dùng chọn một hình ảnh và sau đó thường tiến hành một số trường khác (ví dụ: địa chỉ, tên, ...), do đó khi anh ta nhấn nút "gửi", hình ảnh thường được tải lên. Anh ấy không chờ đợi và xem màn hình nói "tải lên ...".

Điều tương tự cũng xảy ra đối với việc lấy hình ảnh. Đặc biệt nhờ điện thoại di động và dữ liệu di động hạn chế, bạn không muốn gửi hình ảnh gốc, bạn muốn gửi hình ảnh đã thay đổi kích thước để chúng không mất nhiều băng thông (và để làm cho ứng dụng di động của bạn nhanh hơn, bạn thường không muốn để thay đổi kích thước của nó, bạn muốn hình ảnh phù hợp hoàn hảo với chế độ xem của bạn). Vì lý do này, các ứng dụng tốt đang sử dụng một cái gì đó như đám mây (hoặc chúng tôi có máy chủ hình ảnh riêng để thay đổi kích thước).

Ngoài ra, nếu dữ liệu không riêng tư, thì bạn gửi lại cho ứng dụng / giao diện chỉ URL và nó tải trực tiếp từ bộ nhớ đám mây, giúp tiết kiệm rất nhiều băng thông và thời gian xử lý cho máy chủ của bạn. Trong các ứng dụng lớn hơn của chúng tôi, có rất nhiều terabyte được tải xuống mỗi tháng, bạn không muốn xử lý trực tiếp trên mỗi máy chủ API REST, tập trung vào hoạt động CRUD. Bạn muốn xử lý việc đó tại một nơi (Máy chủ hình ảnh của chúng tôi có bộ nhớ đệm, v.v.) hoặc để dịch vụ đám mây xử lý tất cả.


Nhược điểm: "Nhược điểm" duy nhất mà bạn nên nghĩ đến là "hình ảnh không được gán". Người dùng chọn hình ảnh và tiếp tục điền vào các trường khác, nhưng sau đó anh ta nói "nah" và tắt ứng dụng hoặc tab, nhưng trong khi đó bạn đã tải lên hình ảnh thành công. Điều này có nghĩa là bạn đã tải lên một hình ảnh không được chỉ định ở bất cứ đâu.

Có một số cách xử lý này. Cách dễ nhất là "Tôi không quan tâm", đây là một điều có liên quan, nếu điều này không xảy ra thường xuyên hoặc bạn thậm chí có mong muốn lưu trữ mọi người dùng hình ảnh gửi cho bạn (vì bất kỳ lý do nào) và bạn không muốn bất kỳ xóa.

Một số khác cũng dễ dàng - bạn có CRON và tức là mỗi tuần và bạn xóa tất cả các hình ảnh chưa được gán cũ hơn một tuần.


Điều gì sẽ xảy ra nếu [ngay khi bạn chọn hình ảnh, nó bắt đầu tải lên trên nền (thường là POST trên / điểm cuối hình ảnh), trả lại ID cho bạn sau khi tải lên] khi yêu cầu không thành công do kết nối internet? Bạn sẽ nhắc người dùng trong khi họ tiến hành một số trường khác (ví dụ: địa chỉ, tên, ...)? Tôi cá là bạn vẫn sẽ đợi cho đến khi người dùng nhấn nút "gửi" và thử lại yêu cầu của bạn khiến họ chờ trong khi xem màn hình có nội dung "tải lên ...".
Adromil Balais

5
@AdromilBalais - API RESTful không trạng thái, do đó, không có gì (Máy chủ không theo dõi trạng thái của người tiêu dùng). Người tiêu dùng dịch vụ (ví dụ trang web hoặc thiết bị di động) chịu trách nhiệm xử lý các yêu cầu không thành công, do đó, người tiêu dùng phải quyết định nếu nó gọi ngay yêu cầu đó sau khi yêu cầu này không thành công hay phải làm gì (ví dụ: "Tải lên hình ảnh thất bại - muốn thử lại ")
libik

2
Rất nhiều thông tin và khai sáng câu trả lời. Cảm ơn đã trả lời.
Zuhayer Tahir

Điều này không thực sự giải quyết vấn đề ban đầu. Điều này chỉ nói "sử dụng dịch vụ đám mây"
Martin Muzatko

3
@MartinMuzatko - nó, nó chọn tùy chọn thứ hai và cho bạn biết bạn nên sử dụng nó như thế nào và tại sao. Nếu bạn có nghĩa là "nhưng đây không phải là lựa chọn hoàn hảo cho phép bạn gửi mọi thứ trong một yêu cầu và không có hàm ý" - vâng, không có giải pháp nào đáng tiếc như vậy.
libik

103

Có một số quyết định để đưa ra :

  1. Đầu tiên về đường dẫn tài nguyên :

    • Tự mô hình hóa hình ảnh dưới dạng tài nguyên:

      • Lồng nhau trong người dùng (/ user /: id / image): mối quan hệ giữa người dùng và hình ảnh được thực hiện ngầm

      • Trong đường dẫn gốc (/ hình ảnh):

        • Khách hàng chịu trách nhiệm thiết lập mối quan hệ giữa hình ảnh và người dùng, hoặc;

        • Nếu bối cảnh bảo mật được cung cấp với yêu cầu POST được sử dụng để tạo hình ảnh, máy chủ có thể ngầm thiết lập mối quan hệ giữa người dùng được xác thực và hình ảnh.

    • Nhúng hình ảnh như một phần của người dùng

  2. Quyết định thứ hai là về cách thể hiện tài nguyên hình ảnh :

    • Khi tải trọng JSON được mã hóa cơ sở 64
    • Là một tải trọng nhiều phần

Đây sẽ là theo dõi quyết định của tôi:

  • Tôi thường thích thiết kế hơn hiệu suất trừ khi có một trường hợp mạnh cho nó. Nó làm cho hệ thống dễ bảo trì hơn và có thể dễ dàng hiểu hơn bởi các nhà tích hợp.
  • Vì vậy, suy nghĩ đầu tiên của tôi là sử dụng tài nguyên hình ảnh Base64 vì nó cho phép bạn giữ mọi thứ JSON. Nếu bạn chọn tùy chọn này, bạn có thể mô hình hóa đường dẫn tài nguyên theo ý muốn.
    • Nếu mối quan hệ giữa người dùng và hình ảnh là 1 đến 1, tôi muốn mô hình hóa hình ảnh thành một thuộc tính đặc biệt nếu cả hai bộ dữ liệu được cập nhật cùng một lúc. Trong mọi trường hợp khác, bạn có thể tự do chọn mô hình hóa hình ảnh dưới dạng một thuộc tính, cập nhật nó thông qua PUT hoặc PATCH hoặc dưới dạng tài nguyên riêng biệt.
  • Nếu bạn chọn tải trọng nhiều phần, tôi cảm thấy bắt buộc phải mô hình hóa hình ảnh như một tài nguyên trên, để các tài nguyên khác, trong trường hợp của chúng tôi, tài nguyên người dùng, không bị ảnh hưởng bởi quyết định sử dụng biểu diễn nhị phân cho hình ảnh.

Sau đó xuất hiện câu hỏi: Có bất kỳ tác động hiệu suất nào về việc chọn base64 so với nhiều phần không? . Chúng tôi có thể nghĩ rằng việc trao đổi dữ liệu ở định dạng nhiều phần sẽ hiệu quả hơn. Nhưng bài viết này cho thấy làm thế nào ít làm cả hai đại diện khác nhau về kích thước.

Lựa chọn của tôi Base64:

  • Quyết định thiết kế nhất quán
  • Tác động hiệu suất không đáng kể
  • Vì các trình duyệt hiểu URI dữ liệu (hình ảnh được mã hóa base64), không cần phải chuyển đổi chúng nếu máy khách là trình duyệt
  • Tôi sẽ không bỏ phiếu về việc có thuộc tính hay tài nguyên độc lập hay không, tùy thuộc vào miền vấn đề của bạn (mà tôi không biết) và sở thích cá nhân của bạn.

3
Chúng ta không thể mã hóa dữ liệu bằng các giao thức tuần tự hóa khác như protobuf, v.v.? Về cơ bản tôi đang cố gắng để hiểu nếu có những cách đơn giản khác để giải quyết việc tăng kích thước và thời gian xử lý đi kèm với mã hóa base64.
Andy Dufresne

1
Câu trả lời rất hấp dẫn. cảm ơn vì cách tiếp cận từng bước Nó làm cho tôi hiểu điểm của bạn tốt hơn rất nhiều.
Zuhayer Tahir

13

Giải pháp thứ hai của bạn có lẽ là chính xác nhất. Bạn nên sử dụng thông số HTTP và mô phỏng theo cách chúng được dự định và tải tệp qua multipart/form-data. Theo như xử lý các mối quan hệ, tôi sẽ sử dụng quy trình này (hãy nhớ rằng tôi không biết gì về các giả định hoặc thiết kế hệ thống của bạn):

  1. POSTđể /userstạo thực thể người dùng.
  2. POSThình ảnh để /images, đảm bảo trả lại mộtLocation tiêu đề cho nơi hình ảnh có thể được truy xuất theo thông số HTTP.
  3. PATCHđến /users/carPhotovà gán cho nó ID của ảnh được đưa ra trong Locationtiêu đề của bước 2.

1
Tôi không có bất kỳ kiểm soát trực tiếp nào về "khách hàng sẽ sử dụng API như thế nào" ... Vấn đề của điều này là những bức ảnh "chết" không được vá vào một số tài nguyên ...
libik

4
Thông thường khi bạn chọn tùy chọn thứ hai, được ưu tiên tải lên phần tử phương tiện đầu tiên và trả lại định danh phương tiện cho khách hàng, sau đó khách hàng có thể gửi dữ liệu thực thể bao gồm cả định danh phương tiện, cách tiếp cận này tránh thông tin bị hỏng o thông tin không khớp.
Kellerman Rivero

2

Không có giải pháp dễ dàng. Mỗi cách đều có ưu và nhược điểm của chúng. Nhưng cách chính tắc là sử dụng tùy chọn đầu tiên : multipart/form-data. Như hướng dẫn khuyến nghị của W3 nói

Loại nội dung "nhiều dữ liệu / biểu mẫu dữ liệu" nên được sử dụng để gửi biểu mẫu có chứa tệp, dữ liệu không phải ASCII và dữ liệu nhị phân.

Chúng tôi không gửi biểu mẫu, thực sự, nhưng nguyên tắc ngầm vẫn được áp dụng. Sử dụng base64 làm biểu diễn nhị phân, không chính xác vì bạn đang sử dụng công cụ không chính xác để thực hiện mục tiêu của mình, mặt khác, tùy chọn thứ hai buộc các máy khách API của bạn phải thực hiện nhiều công việc hơn để tiêu thụ dịch vụ API của bạn. Bạn nên thực hiện công việc khó khăn ở phía máy chủ để cung cấp API dễ tiêu thụ. Tùy chọn đầu tiên không dễ gỡ lỗi, nhưng khi bạn thực hiện nó, nó có thể không bao giờ thay đổi.

Sử dụng multipart/form-databạn đã gắn bó với triết lý REST / http. Bạn có thể xem một câu trả lời cho câu hỏi tương tự ở đây .

Một tùy chọn khác nếu trộn các lựa chọn thay thế, bạn có thể sử dụng nhiều dữ liệu / biểu mẫu dữ liệu nhưng thay vì gửi từng giá trị riêng biệt, bạn có thể gửi một giá trị có tên là tải trọng với tải trọng json bên trong nó. (Tôi đã thử cách tiếp cận này bằng ASP.NET WebAPI 2 và hoạt động tốt).


2
Hướng dẫn đề xuất W3 đó không liên quan ở đây, vì nó nằm trong bối cảnh của thông số HTML 4.
Johann

1
Rất đúng .... "dữ liệu không phải ASCII" yêu cầu nhiều phần? Trong thế kỷ hai mươi mốt? Trong một thế giới UTF-8? Tất nhiên đó là một khuyến nghị vô lý cho ngày hôm nay. Tôi thậm chí ngạc nhiên rằng đã tồn tại trong HTML 4 ngày, nhưng đôi khi thế giới cơ sở hạ tầng Internet di chuyển rất chậm.
Ray Toal
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.