2010-10-15 44 views
188

Tôi có một dịch vụ web REST hiện cho thấy URL này:Làm cách nào để tải lên tệp có siêu dữ liệu bằng dịch vụ web REST?

http://server/data/media

nơi người dùng có thể POST JSON sau:

{ 
    "Name": "Test", 
    "Latitude": 12.59817, 
    "Longitude": 52.12873 
} 

để tạo ra một phương tiện siêu dữ liệu mới.

Giờ tôi cần có khả năng tải lên tệp cùng lúc với siêu dữ liệu đa phương tiện. Cách tốt nhất để thực hiện điều này là gì? Tôi có thể giới thiệu một tài sản mới được gọi là file và base64 mã hóa tệp, nhưng tôi đã tự hỏi nếu có cách nào tốt hơn.

Cũng đang sử dụng multipart/form-data giống như hình thức HTML sẽ gửi qua, nhưng tôi đang sử dụng dịch vụ web REST và tôi muốn sử dụng JSON nếu có thể.

+26

Gắn bó với chỉ sử dụng JSON là không thực sự cần thiết để có một trang web RESTful dịch vụ. Về cơ bản, REST là bất cứ thứ gì tuân theo các nguyên tắc chính của các phương thức HTTP và một số quy tắc khác (được cho là không được chuẩn hóa). –

Trả lời

150

Tôi đồng ý với Greg rằng cách tiếp cận hai giai đoạn là giải pháp hợp lý, tuy nhiên tôi sẽ thực hiện theo cách khác. Tôi sẽ làm gì:

POST http://server/data/media 
body: 
{ 
    "Name": "Test", 
    "Latitude": 12.59817, 
    "Longitude": 52.12873 
} 

Để tạo các mục nhập siêu dữ liệu và trả về một phản ứng như:

201 Created 
Location: http://server/data/media/21323 
{ 
    "Name": "Test", 
    "Latitude": 12.59817, 
    "Longitude": 52.12873, 
    "ContentUrl": "http://server/data/media/21323/content" 
} 

Khách hàng sau đó có thể sử dụng contentURL này và làm một PUT với dữ liệu tập tin.

Điều thú vị về phương pháp này là khi máy chủ của bạn bắt đầu được cân bằng với khối lượng dữ liệu khổng lồ, url mà bạn quay lại có thể trỏ đến một số máy chủ khác có nhiều dung lượng hơn. Hoặc bạn có thể thực hiện một số loại phương pháp tiếp cận vòng robin nếu băng thông là một vấn đề.

+5

Một lợi thế để gửi nội dung trước tiên là vào thời điểm siêu dữ liệu tồn tại, nội dung đã có sẵn. Cuối cùng câu trả lời đúng tùy thuộc vào việc tổ chức dữ liệu trong hệ thống. –

+0

Cảm ơn, tôi đã đánh dấu đây là câu trả lời đúng vì đây là những gì tôi muốn làm. Thật không may, do quy tắc kinh doanh kỳ lạ, chúng tôi phải cho phép tải lên xảy ra theo bất kỳ thứ tự nào (siêu dữ liệu trước tiên hoặc tệp đầu tiên). Tôi đã tự hỏi nếu có một cách để kết hợp hai để tiết kiệm đau đầu của đối phó với cả hai tình huống. –

+0

@Daniel Nếu bạn POST tệp dữ liệu trước, thì bạn có thể lấy URL được trả lại trong Vị trí và thêm nó vào thuộc tính ContentUrl trong siêu dữ liệu. Bằng cách đó, khi máy chủ nhận được siêu dữ liệu, nếu một ContentUrl tồn tại thì nó đã biết vị trí của tệp. Nếu không có ContentUrl, sau đó nó biết rằng nó sẽ tạo ra một. –

26

Một cách để tiếp cận vấn đề là làm cho quá trình tải lên hai giai đoạn trở lên. Đầu tiên, bạn sẽ tự tải lên tập tin bằng cách sử dụng POST, nơi máy chủ trả về một số định danh trở lại máy khách (một định danh có thể là SHA1 của các nội dung tệp). Sau đó, yêu cầu thứ hai liên kết siêu dữ liệu với dữ liệu tệp:

{ 
    "Name": "Test", 
    "Latitude": 12.59817, 
    "Longitude": 52.12873, 
    "ContentID": "7a788f56fa49ae0ba5ebde780efe4d6a89b5db47" 
} 

Bao gồm dữ liệu tệp base64 được mã hóa vào yêu cầu JSON sẽ làm tăng kích thước của dữ liệu được chuyển 33%. Điều này có thể hoặc có thể không quan trọng tùy thuộc vào kích thước tổng thể của tệp.

Một cách tiếp cận khác có thể là sử dụng POST của dữ liệu tệp thô, nhưng bao gồm bất kỳ siêu dữ liệu nào trong tiêu đề yêu cầu HTTP. Tuy nhiên, điều này rơi một chút ngoài các hoạt động REST cơ bản và có thể khó xử hơn đối với một số thư viện máy khách HTTP.

+0

Bạn có thể sử dụng Ascii85 chỉ tăng 1/4. – Singagirl

91

Chỉ vì bạn không gói toàn bộ cơ thể yêu cầu trong JSON, không có nghĩa là nó không phải RESTful sử dụng multipart/form-data để gửi cả JSON và tập tin (hoặc nhiều file) trong một yêu cầu duy nhất:

curl -F "metadata=<metadata.json" -F "[email protected]" http://example.com/add-file 

ở phía máy chủ (sử dụng Python là ngôn ngữ lập trình ở đây):

class AddFileResource(Resource): 
    def render_POST(self, request): 
     metadata = json.loads(request.args['metadata'][0]) 
     file_body = request.args['file'][0] 
     ... 

tải lên mu file ltiple, nó có thể hoặc là sử dụng "cánh đồng mẫu" riêng biệt cho mỗi:

curl -F "metadata=<metadata.json" -F "[email protected]" -F "[email protected]" http://example.com/add-file 

... trong trường hợp này mã máy chủ sẽ có request.args['file1'][0]request.args['file2'][0]

hay tái sử dụng cùng một cho nhiều :

curl -F "metadata=<metadata.json" -F "[email protected]" -F "[email protected]" http://example.com/add-file 

... trong trường hợp này request.args['files'] sẽ chỉ đơn giản là một danh sách dài 2.

hay thực sự vượt qua nhiều file vào một trường duy nhất trong một đi:

curl -F "metadata=<metadata.json" -F "[email protected],some-other-file.tar.gz" http://example.com/add-file 

... trong trường hợp này request.args['files'] sẽ là một chuỗi có chứa tất cả các file mà bạn sẽ phải phân tích chính mình - không chắc chắn làm thế nào để làm điều đó, nhưng tôi chắc chắn nó không phải là khó khăn, hoặc tốt hơn chỉ cần sử dụng các phương pháp tiếp cận trước đó.

Sự khác biệt giữa @<@ làm cho tệp được đính kèm dưới dạng tệp tải lên, trong khi < đính kèm nội dung của tệp dưới dạng trường văn bản.

P.S. Chỉ vì tôi đang sử dụng curl như một cách để tạo yêu cầu POST không có nghĩa là yêu cầu HTTP chính xác không thể gửi từ ngôn ngữ lập trình như Python hoặc sử dụng bất kỳ công cụ đủ khả năng nào.

+4

Tôi đã tự hỏi về cách tiếp cận này bản thân mình, và tại sao tôi đã không nhìn thấy bất cứ ai khác đưa nó ra được nêu ra. Tôi đồng ý, có vẻ hoàn toàn yên tĩnh với tôi. – soupdog

+1

CÓ! Đây là cách tiếp cận rất thực tế, và nó không phải là bất kỳ RESTful ít hơn bằng cách sử dụng "ứng dụng/json" như là một loại nội dung cho toàn bộ yêu cầu. – sickill

+0

.. nhưng điều đó chỉ có thể nếu bạn có dữ liệu trong a.tập tin json và tải lên nó, không phải là trường hợp – itsjavi

9

Tôi nhận ra đây là một câu hỏi rất cũ, nhưng hy vọng điều này sẽ giúp đỡ người khác khi tôi đến bài đăng này tìm kiếm điều tương tự. Tôi đã có một vấn đề tương tự, chỉ là siêu dữ liệu của tôi là một Guid và int. Các giải pháp là như nhau mặc dù. Bạn chỉ có thể tạo phần siêu dữ liệu cần thiết của URL.

POST phương pháp chấp nhận trong lớp học "điều khiển" của bạn:

tuyến
public Task<HttpResponseMessage> PostFile(string name, float latitude, float longitude) 
{ 
    //See http://stackoverflow.com/a/10327789/431906 for how to accept a file 
    return null; 
} 

Sau đó, trong bất cứ điều gì bạn đang đăng ký, WebApiConfig.Register (HttpConfiguration config) cho tôi trong trường hợp này.

config.Routes.MapHttpRoute(
    name: "FooController", 
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}", 
    defaults: new { } 
); 
2

Nếu tệp của bạn và siêu dữ liệu tạo một tài nguyên, hoàn toàn tốt để tải lên cả hai trong một yêu cầu. Yêu cầu mẫu sẽ là:

POST https://target.com/myresources/resourcename HTTP/1.1 

Accept: application/json 

Content-Type: multipart/form-data; 

boundary=-----------------------------28947758029299 

Host: target.com 

-------------------------------28947758029299 

Content-Disposition: form-data; name="application/json" 

{"markers": [ 
     { 
      "point":new GLatLng(40.266044,-74.718479), 
      "homeTeam":"Lawrence Library", 
      "awayTeam":"LUGip", 
      "markerImage":"images/red.png", 
      "information": "Linux users group meets second Wednesday of each month.", 
      "fixture":"Wednesday 7pm", 
      "capacity":"", 
      "previousScore":"" 
     }, 
     { 
      "point":new GLatLng(40.211600,-74.695702), 
      "homeTeam":"Hamilton Library", 
      "awayTeam":"LUGip HW SIG", 
      "markerImage":"images/white.png", 
      "information": "Linux users can meet the first Tuesday of the month to work out harward and configuration issues.", 
      "fixture":"Tuesday 7pm", 
      "capacity":"", 
      "tv":"" 
     }, 
     { 
      "point":new GLatLng(40.294535,-74.682012), 
      "homeTeam":"Applebees", 
      "awayTeam":"After LUPip Mtg Spot", 
      "markerImage":"images/newcastle.png", 
      "information": "Some of us go there after the main LUGip meeting, drink brews, and talk.", 
      "fixture":"Wednesday whenever", 
      "capacity":"2 to 4 pints", 
      "tv":"" 
     }, 
] } 

-------------------------------28947758029299 

Content-Disposition: form-data; name="name"; filename="myfilename.pdf" 

Content-Type: application/octet-stream 

%PDF-1.4 
% 
2 0 obj 
<</Length 57/Filter/FlateDecode>>stream 
x+r 
26S00SI2P0Qn 
F 
!i\ 
)%[email protected] 
[ 
endstream 
endobj 
4 0 obj 
<</Type/Page/MediaBox[0 0 595 842]/Resources<</Font<</F1 1 0 R>>>>/Contents 2 0 R/Parent 3 0 R>> 
endobj 
1 0 obj 
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>> 
endobj 
3 0 obj 
<</Type/Pages/Count 1/Kids[4 0 R]>> 
endobj 
5 0 obj 
<</Type/Catalog/Pages 3 0 R>> 
endobj 
6 0 obj 
<</Producer(iTextSharp 5.5.11 2000-2017 iText Group NV \(AGPL-version\))/CreationDate(D:20170630120636+02'00')/ModDate(D:20170630120636+02'00')>> 
endobj 
xref 
0 7 
0000000000 65535 f 
0000000250 00000 n 
0000000015 00000 n 
0000000338 00000 n 
0000000138 00000 n 
0000000389 00000 n 
0000000434 00000 n 
trailer 
<</Size 7/Root 5 0 R/Info 6 0 R/ID [<c7c34272c2e618698de73f4e1a65a1b5><c7c34272c2e618698de73f4e1a65a1b5>]>> 
%iText-5.5.11 
startxref 
597 
%%EOF 

-------------------------------28947758029299-- 
Các vấn đề liên quan