How do I upload a file with metadata using a REST web service?
我有一个当前公开此URL的REST Web服务:
http://server/data/media(服务器/数据/媒体)
其中用户可以选择以下JSON:
1 2 3 4 5 | { "Name":"Test", "Latitude": 12.59817, "Longitude": 52.12873 } |
以便创建新的媒体元数据。
现在,我需要能够同时上传一个文件作为媒体元数据。最好的方法是什么?我可以引入一个名为
还有使用
我同意格雷格的观点,一个两阶段的方法是一个合理的解决方案,但是我会反过来做。我会这样做:
1 2 3 4 5 6 7 | POST http://server/data/media body: { "Name":"Test", "Latitude": 12.59817, "Longitude": 52.12873 } |
号
要创建元数据条目并返回如下响应:
1 2 3 4 5 6 7 8 | 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中包装整个请求体,并不意味着使用
1 | curl -F"metadata=<metadata.json" -F"[email protected]" http://example.com/add-file |
。
在服务器端(将python用于伪代码):
1 2 3 4 5 | class AddFileResource(Resource): def render_POST(self, request): metadata = json.loads(request.args['metadata'][0]) file_body = request.args['file'][0] ... |
要上载多个文件,可以对每个文件使用单独的"表单域":
1 | curl -F"metadata=<metadata.json" -F"[email protected]" -F"[email protected]" http://example.com/add-file |
。
…在这种情况下,服务器代码将有
或者对许多人重复使用同一个:
1 | curl -F"metadata=<metadata.json" -F"[email protected]" -F"[email protected]" http://example.com/add-file |
。
…在这种情况下,
或者一次将多个文件传递到一个字段中:
1 | curl -F"metadata=<metadata.json" -F"[email protected],some-other-file.tar.gz" http://example.com/add-file |
…在这种情况下,
另外,仅仅因为我使用
解决这个问题的一种方法是使上传过程分为两个阶段。首先,您将使用post上传文件本身,其中服务器将一些标识符返回给客户机(标识符可能是文件内容的sha1)。然后,第二个请求将元数据与文件数据关联:
1 2 3 4 5 6 | { "Name":"Test", "Latitude": 12.59817, "Longitude": 52.12873, "ContentID":"7a788f56fa49ae0ba5ebde780efe4d6a89b5db47" } |
包括编码到JSON请求本身的文件data base64,将使传输数据的大小增加33%。根据文件的总体大小,这可能很重要,也可能不重要。
另一种方法可能是使用原始文件数据的日志,但在HTTP请求头中包含任何元数据。但是,这有点超出了基本的REST操作,对于某些HTTP客户机库来说可能会更尴尬。
我意识到这是一个非常古老的问题,但希望这能帮助其他人,因为我在这篇文章中寻找相同的东西。我有一个类似的问题,我的元数据是一个guid和int,但是解决方案是相同的。您可以将所需的元数据作为URL的一部分。
在"控制器"类中发布接受方法:
1 2 3 4 5 | 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(httpconfiguration-config)。
1 2 3 4 5 | config.Routes.MapHttpRoute( name:"FooController", routeTemplate:"api/{controller}/{name}/{latitude}/{longitude}", defaults: new { } ); |
如果您的文件及其元数据创建了一个资源,那么可以在一个请求中同时上载它们。样品要求如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 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-- |
。
我不明白为什么在过去的八年里,没有人发布过简单的答案。不是将文件编码为base64,而是将JSON编码为字符串。然后在服务器端对JSON进行解码。
在javascript中:
1 2 3 | let formData = new FormData(); formData.append("file", myfile); formData.append("myjson", JSON.stringify(myJsonObject)); |
号
使用内容类型发布:多部分/表单数据
在服务器端,正常地检索文件,并以字符串的形式检索JSON。将字符串转换为一个对象,该对象通常是一行代码,无论您使用什么编程语言。
(是的,效果很好。在我的一个应用程序中进行。)