Program Tip

파일 및 관련 데이터를 가급적 JSON으로 RESTful 웹 서비스에 게시

programtip 2020. 9. 30. 11:21
반응형

파일 및 관련 데이터를 가급적 JSON으로 RESTful 웹 서비스에 게시


이것은 아마도 어리석은 질문이 될 것이지만 나는 그날 밤을 보내고 있습니다. 응용 프로그램에서 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 = "사용자"

"사용자"필드의 항목에 해당하는 값으로.

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

multipart / form-data 사용의 좋은 점은 HTTP 정의 헤더를 사용하고 있으므로 기존 HTTP 도구를 사용하여 서비스를 만드는 REST 철학을 고수하고 있다는 것입니다.


이 스레드가 꽤 오래되었다는 것을 알고 있지만 여기에 한 가지 옵션이 없습니다. 업로드 할 데이터와 함께 전송할 메타 데이터 (모든 형식)가있는 경우 단일 multipart/related요청을 할 수 있습니다 .

Multipart / Related 미디어 유형은 여러 상호 관련된 신체 부분으로 구성된 복합 개체를위한 것입니다.

자세한 내용은 RFC 2387 사양을 확인할 수 있습니다 .

기본적으로 이러한 요청의 각 부분은 다른 유형의 콘텐츠를 가질 수 있으며 모든 부분은 어떻게 든 관련이 있습니다 (예 : 이미지 및 IT 메타 데이터). 부품은 경계 문자열로 식별되며 최종 경계 문자열 뒤에는 두 개의 하이픈이옵니다.

예:

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입니다 (예제 사용)-보시다시피 API에서 file_id (서버에 업로드 된 파일 식별자)를 사용하지 않습니다.

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.Read photos list

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

4.Read some photo details

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

5.Read photo file

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

So the conclusion is that, first you create object (photo) by POST, and then you send second request with file (again POST).


Since the only missing example is the ANDROID example, I'll add it. This technique uses a custom AsyncTask that should be declared inside your Activity class.

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

}

So, when you want to upload your file just call:

new UploadFile().execute();

FormData Objects: Upload Files Using Ajax

XMLHttpRequest Level 2 adds support for the new FormData interface. FormData objects provide a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest send() method.

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


I wanted send some strings to backend server. I didnt use json with multipart, I have used request params.

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void uploadFile(HttpServletRequest request,
        HttpServletResponse response, @RequestParam("uuid") String uuid,
        @RequestParam("type") DocType type,
        @RequestParam("file") MultipartFile uploadfile)

Url would look like

http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT

I am passing two params (uuid and type) along with file upload. Hope this will help who don't have the complex json data to send.


@RequestMapping(value = "/uploadImageJson", method = RequestMethod.POST)
    public @ResponseBody Object jsongStrImage(@RequestParam(value="image") MultipartFile image, @RequestParam String jsonStr) {
-- use  com.fasterxml.jackson.databind.ObjectMapper convert Json String to Object
}

Please ensure that you have following import. Ofcourse other standard imports

import org.springframework.core.io.FileSystemResource


    void uploadzipFiles(String token) {

        RestBuilder rest = new RestBuilder(connectTimeout:10000, readTimeout:20000)

        def zipFile = new File("testdata.zip")
        def Id = "001G00000"
        MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
        form.add("id", id)
        form.add('file',new FileSystemResource(zipFile))
        def urld ='''http://URL''';
        def resp = rest.post(urld) {
            header('X-Auth-Token', clientSecret)
            contentType "multipart/form-data"
            body(form)
        }
        println "resp::"+resp
        println "resp::"+resp.text
        println "resp::"+resp.headers
        println "resp::"+resp.body
        println "resp::"+resp.status
    }

참고URL : https://stackoverflow.com/questions/4083702/posting-a-file-and-associated-data-to-a-restful-webservice-preferably-as-json

반응형