json 如何使用 REST Web 服务上传带有元数据的文件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3938569/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:36:17  来源:igfitidea点击:

How do I upload a file with metadata using a REST web service?

web-servicesjsonrestfile-upload

提问by Daniel T.

I have a REST web service that currently exposes this URL:

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

http://server/data/media

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

where users can POSTthe following JSON:

用户可以POST在其中使用以下 JSON:

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

in order to create a new Media metadata.

以创建新的媒体元数据。

Now I need the ability to upload a file at the same time as the media metadata. What's the best way of going about this? I could introduce a new property called fileand base64 encode the file, but I was wondering if there was a better way.

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

There's also using multipart/form-datalike what a HTML form would send over, but I'm using a REST web service and I want to stick to using JSON if at all possible.

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

采纳答案by Darrel Miller

I agree with Greg that a two phase approach is a reasonable solution, however I would do it the other way around. I would do:

我同意 Greg 的观点,即两阶段方法是一个合理的解决方案,但我会反其道而行之。我会做:

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

To create the metadata entry and return a response like:

创建元数据条目并返回如下响应:

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

The client can then use this ContentUrl and do a PUT with the file data.

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

The nice thing about this approach is when your server starts get weighed down with immense volumes of data, the url that you return can just point to some other server with more space/capacity. Or you could implement some kind of round robin approach if bandwidth is an issue.

这种方法的好处是,当您的服务器开始因大量数据而不堪重负时,您返回的 url 可以指向其他具有更多空间/容量的服务器。或者,如果带宽是一个问题,您可以实施某种循环方法。

回答by Erik Kaplun

Just because you're not wrapping the entire request body in JSON, doesn't meant it's not RESTful to use multipart/form-datato post both the JSON and the file(s) in a single request:

仅仅因为您没有将整个请求正文包装在 JSON 中,并不意味着它不是 RESTful 用于multipart/form-data在单个请求中同时发布 JSON 和文件:

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

on the server side(using Python for pseudocode):

在服务器端(使用 Python 作为伪代码):

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

to upload multiple files, it's possible to either use separate "form fields" for each:

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

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

...in which case the server code will have request.args['file1'][0]and request.args['file2'][0]

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

or reuse the same one for many:

或为许多人重用相同的:

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

...in which case request.args['files']will simply be a list of length 2.

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

or actually pass multiple files into a single field in one go:

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

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

...in which case request.args['files']will be a string containing all the files, which you'll have to parse yourself — not sure how to do it, but I'm sure it's not difficult, or better just use the previous approaches.

...在这种情况下,request.args['files']将是一个包含所有文件的字符串,您必须自己解析这些文件 - 不知道该怎么做,但我确定这并不困难,或者最好使用以前的方法。

The difference between @and <is that @causes the file to get attached as a file upload, whereas <attaches the contents of the file as a text field.

之间的区别@<@导致文件得到重视的文件上传,而<武官文件为文本字段的内容。

P.S.Just because I'm using curlas a way to generate the POSTrequests doesn't mean the exact same HTTP requests couldn't be sent from a programming language such as Python or using any sufficiently capable tool.

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

回答by Greg Hewgill

One way to approach the problem is to make the upload a two phase process. First, you would upload the file itself using a POST, where the server returns some identifier back to the client (an identifier might be the SHA1 of the file contents). Then, a second request associates the metadata with the file data:

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

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

Including the file data base64 encoded into the JSON request itself will increase the size of the data transferred by 33%. This may or may not be important depending on the overall size of the file.

将文件数据库 base64 编码到 JSON 请求本身会使传输的数据大小增加 33%。这可能重要也可能不重要,具体取决于文件的整体大小。

Another approach might be to use a POST of the raw file data, but include any metadata in the HTTP request header. However, this falls a bit outside basic REST operations and may be more awkward for some HTTP client libraries.

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

回答by Greg Biles

I realize this is a very old question, but hopefully this will help someone else out as I came upon this post looking for the same thing. I had a similar issue, just that my metadata was a Guid and int. The solution is the same though. You can just make the needed metadata part of the URL.

我意识到这是一个非常古老的问题,但希望这能帮助其他人,因为我在这篇文章中寻找相同的东西。我有一个类似的问题,只是我的元数据是一个 Guid 和 int。不过解决方法是一样的。您可以只将所需的元数据作为 URL 的一部分。

POST accepting method in your "Controller" class:

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

Then in whatever you're registering routes, WebApiConfig.Register(HttpConfiguration config) for me in this case.

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

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

回答by Mike Ezzati

If your file and its metadata creating one resource, its perfectly fine to upload them both in one request. Sample request would be :

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

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

回答by ccleve

I don't understand why, over the course of eight years, no one has posted the easy answer. Rather than encode the file as base64, encode the json as a string. Then just decode the json on the server side.

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

In Javascript:

在 JavaScript 中:

let formData = new FormData();
formData.append("file", myfile);
formData.append("myjson", JSON.stringify(myJsonObject));

POST it using Content-Type: multipart/form-data

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

On the server side, retrieve the file normally, and retrieve the json as a string. Convert the string to an object, which is usually one line of code no matter what programming language you use.

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

(Yes, it works great. Doing it in one of my apps.)

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