[json] JSON과 같이 RESTful WebService에 파일 및 관련 데이터 게시

이것은 아마도 어리석은 질문 일 것입니다. 그러나 나는 그 밤 중 하나를 보내고 있습니다. 응용 프로그램에서 RESTful API를 개발 중이며 클라이언트가 데이터를 JSON으로 보내길 원합니다. 이 응용 프로그램의 일부로 클라이언트는 이미지에 대한 정보뿐만 아니라 파일 (일반적으로 이미지)을 업로드해야합니다.

단일 요청에서 이것이 어떻게 발생하는지 추적하는 데 어려움을 겪고 있습니다. 파일 데이터를 JSON 문자열로 Base64 할 수 있습니까? 서버에 2 개의 게시물을 수행해야합니까? 이것을 위해 JSON을 사용해서는 안됩니까?

참고로 백엔드에서 Grails를 사용하고 있으며 이러한 서비스는 기본 모바일 클라이언트 (iPhone, Android 등)에서 액세스하는 경우 차이가 있습니다.



답변

나는 비슷한 질문을했다 :

REST 웹 서비스를 사용하여 메타 데이터가있는 파일을 어떻게 업로드합니까?

기본적으로 세 가지 선택이 있습니다.

  1. Base64는 데이터 크기를 약 33 % 증가시키는 비용으로 파일을 인코딩하고 서버 / 클라이언트에서 인코딩 / 디코딩을 위해 처리 오버 헤드를 추가합니다.
  2. multipart/form-dataPOST 에서 파일을 먼저 보내고 클라이언트에 ID를 반환하십시오. 그런 다음 클라이언트는 메타 데이터를 ID와 함께 보내고 서버는 파일과 메타 데이터를 다시 연결합니다.
  3. 메타 데이터를 먼저 보내고 클라이언트에게 ID를 반환하십시오. 그런 다음 클라이언트는 파일을 ID로 보내면 서버는 파일과 메타 데이터를 다시 연결합니다.

답변

multipart / form-data 컨텐츠 유형을 사용하여 한 번의 요청으로 파일 및 데이터를 전송할 수 있습니다 .

많은 응용 프로그램에서 사용자에게 양식을 제공 할 수 있습니다. 사용자는 입력하거나 사용자 입력으로 생성하거나 사용자가 선택한 파일에 포함 된 정보를 포함하여 양식을 작성합니다. 양식이 작성되면 양식의 데이터가 사용자에서 수신 애플리케이션으로 전송됩니다.

MultiPart / Form-Data의 정의는 해당 응용 프로그램 중 하나에서 파생됩니다 …

에서 http://www.faqs.org/rfcs/rfc2388.html :

“multipart / form-data”에는 일련의 파트가 포함됩니다. 각 부분에는 처리 유형이 “form-data”이고 내용에 “name”의 (추가) 매개 변수가 포함 된 콘텐츠 처리 헤더 [RFC 2183]가 있어야합니다. 여기서 해당 매개 변수의 값은 원본입니다. 양식의 필드 이름. 예를 들어 부품에 헤더가 포함될 수 있습니다.

내용 처리 : 양식 데이터; name = “user”

“사용자”필드의 입력에 해당하는 값으로.

경계 사이의 각 섹션 내에 파일 정보 또는 필드 정보를 포함 할 수 있습니다. 사용자가 데이터와 양식을 모두 제출 해야하는 RESTful 서비스를 성공적으로 구현했으며 multipart / form-data가 완벽하게 작동했습니다. 이 서비스는 Java / Spring을 사용하여 구축되었으며 클라이언트는 C #을 사용하고 있었으므로 불행히도 서비스 설정 방법에 관한 Grails 예제가 없습니다. 이 경우 각 “form-data”섹션에 매개 변수 이름과 값을 지정할 수있는 위치가 제공되므로 JSON을 사용할 필요가 없습니다.

multipart / form-data 사용에 대한 좋은 점은 HTTP 정의 헤더를 사용한다는 것입니다. 따라서 기존 HTTP 도구를 사용하여 서비스를 작성한다는 REST 철학을 고수하고 있습니다.


답변

이 스레드가 상당히 오래되었다는 것을 알고 있지만 여기에 한 가지 옵션이 없습니다. 업로드 할 데이터와 함께 전송하려는 메타 데이터 (모든 형식)가있는 경우 단일 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--


답변

나는이 질문이 오래되었다는 것을 알고 있지만 마지막 날 에이 동일한 질문을 해결하기 위해 전체 웹을 검색했습니다. 그림, 제목 및 설명을 보내는 REST 웹 서비스 및 iPhone 클라이언트가 있습니다.

내 접근 방식이 최선인지는 모르겠지만 너무 쉽고 간단합니다.

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

}

미래에 문제가 있는지 모르겠지만 현재 프로덕션 환경에서 잘 작동하고 있습니다.


답변

다음은 내 접근 API입니다 (예 : 사용 예). 보시 file_id다시피 API에서 (업로드 된 파일 식별자)를 사용하지 않습니다 .

  1. photo서버에서 객체를 만듭니다 .

    POST: /projects/{project_id}/photos
    body: { name: "some_schema.jpg", comment: "blah"}
    response: photo_id
  2. 파일 업로드 ( file사진 당 하나만 있기 때문에 단수형입니다) :

    POST: /projects/{project_id}/photos/{photo_id}/file
    body: file to upload
    response: -

그리고 예를 들어 :

  1. 사진 목록 읽기

    GET: /projects/{project_id}/photos
    response: [ photo, photo, photo, ... ] (array of objects)
  2. 일부 사진 정보 읽기

    GET: /projects/{project_id}/photos/{photo_id}
    response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
  3. 사진 파일 읽기

    GET: /projects/{project_id}/photos/{photo_id}/file
    response: file content

결론은 먼저 POST로 객체 (사진)를 만든 다음 파일 (POST)과 함께 두 번째 요청을 보냅니다.


답변

FormData 객체 : Ajax를 사용하여 파일 업로드

XMLHttpRequest 레벨 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


답변

유일하게 누락 된 예제는 ANDROID 예제 이므로 추가하겠습니다. 이 기술은 Activity 클래스 내에서 선언 해야하는 사용자 정의 AsyncTask를 사용합니다.

private class UploadFile extends AsyncTask<Void, Integer, String> {
    @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();