当前位置:  开发笔记 > 编程语言 > 正文

将文件和关联数据发布到RESTful WebService,最好是JSON

如何解决《将文件和关联数据发布到RESTfulWebService,最好是JSON》经验,为你挑选了7个好方法。

这可能是一个愚蠢的问题,但我有一个晚上.在我正在开发RESTful API的应用程序中,我们希望客户端以JSON格式发送数据.此应用程序的一部分要求客户端上载文件(通常是图像)以及有关图像的信息.

我很难跟踪单个请求中如何发生这种情况.是否可以将文件数据Base64转换为JSON字符串?我是否需要向服务器发送2个帖子?我不应该为此使用JSON吗?

作为旁注,我们在后端使用Grails,这些服务由本机移动客户端(iPhone,Android等)访问,如果其中任何一个有所不同.



1> Daniel T...:

我在这里问了一个类似的问题:

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

你基本上有三个选择:

    Base64对文件进行编码,代价是将数据大小增加约33%.

    首先在multipart/form-dataPOST中发送文件,然后将ID返回给客户端.然后,客户端发送带有ID的元数据,服务器将文件和元数据重新关联.

    首先发送元数据,然后将ID返回给客户端.然后,客户端发送带有ID的文件,服务器将文件和元数据重新关联.


如果我选择了选项1,我是否只在JSON字符串中包含Base64内容?{file:'234JKFDS#$ @#$ MFDDMS ....',名称:'somename'...}或者还有更多内容吗?
正如你所说的那样,Gregg只是将它作为属性包含在内,而值将是base64编码的字符串.这可能是最简单的方法,但根据文件大小可能不实用.例如,对于我们的应用程序,我们需要发送每个2-3 MB的iPhone映像.增加33%是不可接受的.如果您只发送小的20KB图像,那么开销可能更容易接受.
我还要提一下,base64编码/解码也需要一些处理时间.这可能是最简单的事情,但它肯定不是最好的.
为什么拒绝在一个请求中使用multipart/form-data?
json与base64?嗯..我正在考虑坚持使用multipart/form
选项4:修改您的WebService API,以接受JSON或multipart / form-data输入。
我想在此处添加第4个选项-消除对选项#2和#3的发送顺序的需要:在客户端上创建UUID。使用此UUID发送文件/使用此UUID发送元数据(以任何顺序)。这种方法的“功能”是客户端负责创建UUID-但是无论如何通常都需要使用它来帮助同步。
对我来说,第四个选项是,在multipart/form-data中添加另一个类型为'TEXT'的表单数据并使用字符串化的元数据.

2> McStretch..:

您可以使用multipart/form-data 内容类型在一个请求中发送文件和数据:

在许多应用中,可以向用户呈现表格.用户将填写表单,包括键入的信息,由用户输入生成的信息,或者包含在用户选择的文件中的信息.填写表单后,表单中的数据将从用户发送到接收应用程序.

MultiPart/Form-Data的定义来自其中一个应用程序......

来自http://www.faqs.org/rfcs/rfc2388.html:

"multipart/form-data"包含一系列部分.每个部分都应包含内容处置标题[RFC 2183],其中处置类型为"form-data",并且处置包含"name"的(附加)参数,其中该参数的值为原始值表单中的字段名称.例如,部件可能包含标头:

内容处理:表格数据; 名称="用户"

具有与"user"字段的条目对应的值.

您可以在边界之间的每个部分中包含文件信息或字段信息.我已经成功实现了RESTful服务,该服务要求用户提交数据和表单,并且multipart/form-data完美地工作.该服务是使用Java/Spring构建的,客户端使用的是C#,所以很遗憾,我没有任何Grails示例可以为您提供有关如何设置服务的信息.在这种情况下,您不需要使用JSON,因为每个"form-data"部分都为您提供了指定参数名称及其值的位置.

使用multipart/form-data的好处在于您使用的是HTTP定义的头文件,因此您坚持使用现有HTTP工具创建服务的REST理念.


是的,这基本上是我的答案"我不应该为此使用JSON吗?" 是否有特定原因要求客户端使用JSON?
如果它伤害了某些.Net开发人员的感觉,我为我所说的道歉.虽然英语不是我的母语,但对于我说这项技术本身的粗鲁行为并不是一个有效的借口.使用表单数据非常棒,如果你继续使用它,你也会更加棒极了!
最有可能是业务要求或保持一致性.当然,理想的做法是基于Content-Type HTTP标头接受(表单数据和JSON响应).
选择JSON会在客户端和服务器端产生更优雅的代码,从而减少潜在的错误.表格数据是昨天的.

3> pgiecek..:

我知道这个帖子很老了,但是,我在这里缺少一个选项.如果您要将要发送的元数据(以任何格式)与要上载的数据一起发送,则可以发出单个multipart/related请求.

Multipart/Related媒体类型适用于由多个相互关联的身体部位组成的复合对象.

您可以查看RFC 2387规范以获取更深入的详细信息.

基本上,这种请求的每个部分可以具有不同类型的内容,并且所有部分都以某种方式相关(例如,图像和它的元数据).部件由边界字符串标识,最后的边界字符串后跟两个连字符.

例:

POST /upload HTTP/1.1
Host: www.hostname.com
Content-Type: multipart/related; boundary=xyz
Content-Length: [actual-content-length]

--xyz
Content-Type: application/json; charset=UTF-8

{
    "name": "Sample image",
    "desc": "...",
    ...
}

--xyz
Content-Type: image/jpeg

[image data]
[image data]
[image data]
...
--foo_bar_baz--



4> 小智..:

我知道这个问题已经过时了,但在最后几天我搜索了整个网络以解决同样的问题.我有grails REST webservices和iPhone Client发送图片,标题和描述.

我不知道我的方法是否最好,但是如此简单易行.

我使用UIImagePickerController拍照并使用请求的标头标签向服务器发送NSData以发送图片的数据.

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"myServerAddress"]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:UIImageJPEGRepresentation(picture, 0.5)];
[request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"myPhotoTitle" forHTTPHeaderField:@"Photo-Title"];
[request setValue:@"myPhotoDescription" forHTTPHeaderField:@"Photo-Description"];

NSURLResponse *response;

NSError *error;

[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

在服务器端,我使用以下代码收到照片:

InputStream is = request.inputStream

def receivedPhotoFile = (IOUtils.toByteArray(is))

def photo = new Photo()
photo.photoFile = receivedPhotoFile //photoFile is a transient attribute
photo.title = request.getHeader("Photo-Title")
photo.description = request.getHeader("Photo-Description")
photo.imageURL = "temp"    

if (photo.save()) {    

    File saveLocation = grailsAttributes.getApplicationContext().getResource(File.separator + "images").getFile()
    saveLocation.mkdirs()

    File tempFile = File.createTempFile("photo", ".jpg", saveLocation)

    photo.imageURL = saveLocation.getName() + "/" + tempFile.getName()

    tempFile.append(photo.photoFile);

} else {

    println("Error")

}

我不知道将来是否有问题,但现在在生产环境中工作正常.



5> Kamil Kiełcz..:

这是我的方法API(我使用示例) - 正如您所看到的,您在API中不使用任何file_id(在服务器中上传的文件identyicator):

1.在服务器上创建"照片"对象:

POST: /projects/{project_id}/photos   
params in: {name:some_schema.jpg, comment:blah}
return: photo_id

2.上传文件(注意'文件'是单数形式,因为每张照片只有一个):

POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -

然后例如:

3.阅读照片列表

GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]

4.阅读一些照片细节

GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}

5.阅读照片文件

GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content

所以结论是,首先你通过POST创建对象(照片),然后你发送带文件的secod请求(再次POST).


这似乎是实现这一目标的更"'RESTFUL'方式.

6> lifeisfoo..:

由于唯一缺少的例子是ANDROID示例,我将添加它.此技术使用应在Activity类中声明的自定义AsyncTask.

private class UploadFile extends AsyncTask {
    @Override
    protected void onPreExecute() {
        // set a status bar or show a dialog to the user here
        super.onPreExecute();
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // progress[0] is the current status (e.g. 10%)
        // here you can update the user interface with the current status
    }

    @Override
    protected String doInBackground(Void... params) {
        return uploadFile();
    }

    private String uploadFile() {

        String responseString = null;
        HttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://example.com/upload-file");

        try {
            AndroidMultiPartEntity ampEntity = new AndroidMultiPartEntity(
                new ProgressListener() {
                    @Override
                        public void transferred(long num) {
                            // this trigger the progressUpdate event
                            publishProgress((int) ((num / (float) totalSize) * 100));
                        }
            });

            File myFile = new File("/my/image/path/example.jpg");

            ampEntity.addPart("fileFieldName", new FileBody(myFile));

            totalSize = ampEntity.getContentLength();
            httpPost.setEntity(ampEntity);

            // Making server call
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                responseString = EntityUtils.toString(httpEntity);
            } else {
                responseString = "Error, http status: "
                        + statusCode;
            }

        } catch (Exception e) {
            responseString = e.getMessage();
        }
        return responseString;
    }

    @Override
    protected void onPostExecute(String result) {
        // if you want update the user interface with upload result
        super.onPostExecute(result);
    }

}

所以,当你想上传你的文件时,只需致电:

new UploadFile().execute();



7> lakhan_Ideav..:

FormData对象:使用Ajax上传文件

XMLHttpRequest Level 2增加了对新FormData接口的支持.FormData对象提供了一种方法,可以轻松构造一组表示表单字段及其值的键/值对,然后可以使用XMLHttpRequest send()方法轻松发送.

function AjaxFileUpload() {
    var file = document.getElementById("files");
    //var file = fileInput;
    var fd = new FormData();
    fd.append("imageFileData", file);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/ws/fileUpload.do');
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
             alert('success');
        }
        else if (uploadResult == 'success')
             alert('error');
    };
    xhr.send(fd);
}

https://developer.mozilla.org/en-US/docs/Web/API/FormData

推荐阅读
小色米虫_524
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有