如何使用REST Web服务上传包含元数据的文件?

我有一个REST Web服务,当前公开此URL:

HTTP://服务器/数据/媒体

用户可以在其中发布以下JSON:

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

以创build新的媒体元数据。

现在我需要能够与媒体元数据同时上传文件。 什么是这样做的最好方法? 我可以引入一个名为file和base64的新属性,但是我想知道是否有更好的方法。

也有使用multipart/form-data如HTML表单将发送,但我使用REST Web服务,我想坚持使用JSON,如果可能的话。

我同意格雷格的意见,两阶段的方法是一个合理的解决scheme,但我会反过来做。 我会做:

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

要创build元数据条目并返回如下所示的响应:

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

然后客户端可以使用这个ContentUrl,并用文件数据做PUT。

关于这种方法的好处是,当你的服务器开始被大量的数据压低时,你返回的url可以指向其他具有更多空间/容量的服务器。 或者,如果带宽是一个问题,你可以实现某种循环方法。

仅仅因为你没有用JSON包装整个请求体,并不意味着使用multipart/form-data在一个请求中同时发布JSON和文件(或多个文件)是不是RESTful:

 curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file 

在服务器端 (这里使用Python作为编程语言):

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

要上传多个文件,可以为每个文件使用单独的“表单字段”:

 curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file 

…在这种情况下,服务器代码将有request.args['file1'][0]request.args['file2'][0]

或者重复使用同一个:

 curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz" -F "files=@some-other-file.tar.gz" http://example.com/add-file 

…在这种情况下, request.args['files']将只是一个长度为2的列表。

或者一次性将多个文件实际传递到单个字段中:

 curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz,some-other-file.tar.gz" http://example.com/add-file 

…在这种情况下, request.args['files']将是一个包含所有文件的string,您必须自己parsing – 不知道该怎么做,但我相信这并不困难,或者更好只是使用以前的方法。

@<的不同之处在于@使文件作为file upload来附加,而<作为文本字段附加文件的内容。

PS只是因为我使用curl作为生成POST请求的方式并不意味着不能从Python等编程语言或使用任何function强大的工具发送完全相同的HTTP请求。

解决这个问题的一种方法是使上传成为一个两阶段的过程。 首先,您可以使用POST上传文件,服务器将某个标识符返回给客户端(标识符可能是文件内容的SHA1)。 然后,第二个请求将元数据与文件数据相关联:

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

包括编码到JSON请求本身的文件数据base64将增加33%传输的数据的大小。 根据文件的整体大小,这可能也可能不重要。

另一种方法可能是使用原始文件数据的POST,但在HTTP请求头中包含任何元数据。 但是,这种情况在基本的REST操作之外就会有所下降,对于某些HTTP客户端库来说可能会更加尴尬。

我意识到这是一个非常古老的问题,但希望这将帮助别人,因为我在这个post寻找同样的事情。 我有一个类似的问题,只是我的元数据是Guid和int。 虽然解决scheme是相同的。 您只需将URL所需的元数据部分。

您的“控制器”类中的POST接受方法:

 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; } 

然后在无论你注册路由,WebApiConfig.Register(HttpConfigurationconfiguration)为我在这种情况下。

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

如果你的文件和它的元数据创build一个资源,那么在一个请求中上传它们是完全正确的。 示例请求将是:

 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\ )%!Y0i@.k [ 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--