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

Dan*_* T. 233 rest json web-services file-upload

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

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

用户可以POST使用以下JSON:

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}
Run Code Online (Sandbox Code Playgroud)

为了创建新的媒体元数据.

现在我需要能够在媒体元数据的同时上传文件.解决这个问题的最佳方法是什么?我可以引入一个名为filebase64 的新属性编码文件,但我想知道是否有更好的方法.

还有multipart/form-data像HTML表单一样使用,但我正在使用REST Web服务,我想尽可能坚持使用JSON.

Dar*_*ler 180

我同意格雷格认为两阶段方法是一个合理的解决方案,但我会反过来做.我会做:

POST http://server/data/media
body:
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}
Run Code Online (Sandbox Code Playgroud)

要创建元数据条目并返回响应,例如:

201 Created
Location: http://server/data/media/21323
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentUrl": "http://server/data/media/21323/content"
}
Run Code Online (Sandbox Code Playgroud)

然后,客户端可以使用此ContentUrl并使用文件数据执行PUT.

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

  • 首先发送内容的一个优点是,在元数据存在时,内容已经存在.最终,正确的答案取决于系统中数据的组织. (8认同)

Eri*_*lun 98

仅仅因为您没有将整个请求主体包装在JSON中,并不意味着用于multipart/form-data在单个请求中发布JSON和文件(或多个文件)并不是RESTful :

curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file
Run Code Online (Sandbox Code Playgroud)

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

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

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

curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file
Run Code Online (Sandbox Code Playgroud)

...在这种情况下,服务器代码将具有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
Run Code Online (Sandbox Code Playgroud)

......在这种情况下,它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
Run Code Online (Sandbox Code Playgroud)

...在这种情况下request.args['files']将是一个包含所有文件的字符串,你必须自己解析 - 不知道如何做,但我确信这并不困难,或者更好地使用以前的方法.

@和之间的区别<@导致文件作为文件上载<附加,而将文件的内容作为文本字段附加.

PS仅仅因为我curl用作生成POST请求的方式并不意味着无法从Python等编程语言或使用任何足够强大的工具发送完全相同的HTTP请求.

  • 我自己一直想知道这个方法,为什么我还没有看到其他人说出来.我同意,对我来说似乎很完美. (4认同)
  • @mjolnic你的评论无关紧要:cURL的例子很好,_examples_; 答案明确指出你可以使用任何东西来发送请求......还有什么阻止你只写`curl -f'metadata = {"foo":"bar"}'`? (4认同)
  • 我正在使用这种方法,因为接受的答案对我正在开发的应用程序不起作用(该文件在数据之前不存在,并且它增加了不必要的复杂性来处理首先上载数据并且文件永远不会上传的情况) . (3认同)
  • 是的!这是非常实用的方法,它并不比使用“application/json”作为整个请求的内容类型更 RESTful。 (2认同)

Gre*_*ill 32

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

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentID": "7a788f56fa49ae0ba5ebde780efe4d6a89b5db47"
}
Run Code Online (Sandbox Code Playgroud)

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

另一种方法可能是使用原始文件数据的POST,但在HTTP请求标头中包含任何元数据.但是,这有点落在基本的REST操作之外,对某些HTTP客户端库来说可能更尴尬.

  • @jam01:巧合的是,我昨天刚刚看到一些内容很好地回答了空间问题:[Base64编码的空间开销是多少?](https://lemire.me/blog/2019/01/30/what-is-the -base64 编码的空间开销/) (4认同)

Gre*_*les 10

我意识到这是一个非常古老的问题,但希望这会帮助其他人,因为我在这篇文章中寻找同样的事情.我有一个类似的问题,只是我的元数据是Guid和int.解决方案是一样的.您只需在URL中创建所需的元数据即可.

"Controller"类中的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;
}
Run Code Online (Sandbox Code Playgroud)

然后在你注册路由的任何地方,WebApiConfig.Register(HttpConfiguration config)对我来说就是这种情况.

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


Mik*_*ati 7

如果您的文件及其元数据创建了一种资源,则可以在一个请求中同时上传它们。示例请求将是:

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--
Run Code Online (Sandbox Code Playgroud)


ccl*_*eve 7

我不明白为什么在八年的时间里,没有人发布简单的答案。不是将文件编码为 base64,而是将 json 编码为字符串。然后只需在服务器端解码json。

在 JavaScript 中:

let formData = new FormData();
formData.append("file", myfile);
formData.append("myjson", JSON.stringify(myJsonObject));
Run Code Online (Sandbox Code Playgroud)

使用内容类型发布它:multipart/form-data

在服务器端,正常检索文件,将json作为字符串检索。将字符串转换为对象,无论您使用何种编程语言,这通常都是一行代码。

(是的,它工作得很好。在我的一个应用程序中这样做。)