[json] JSON과 같이 RESTful WebService에 파일 및 관련 데이터 게시
이것은 아마도 어리석은 질문 일 것입니다. 그러나 나는 그 밤 중 하나를 보내고 있습니다. 응용 프로그램에서 RESTful API를 개발 중이며 클라이언트가 데이터를 JSON으로 보내길 원합니다. 이 응용 프로그램의 일부로 클라이언트는 이미지에 대한 정보뿐만 아니라 파일 (일반적으로 이미지)을 업로드해야합니다.
단일 요청에서 이것이 어떻게 발생하는지 추적하는 데 어려움을 겪고 있습니다. 파일 데이터를 JSON 문자열로 Base64 할 수 있습니까? 서버에 2 개의 게시물을 수행해야합니까? 이것을 위해 JSON을 사용해서는 안됩니까?
참고로 백엔드에서 Grails를 사용하고 있으며 이러한 서비스는 기본 모바일 클라이언트 (iPhone, Android 등)에서 액세스하는 경우 차이가 있습니다.
답변
나는 비슷한 질문을했다 :
REST 웹 서비스를 사용하여 메타 데이터가있는 파일을 어떻게 업로드합니까?
기본적으로 세 가지 선택이 있습니다.
- Base64는 데이터 크기를 약 33 % 증가시키는 비용으로 파일을 인코딩하고 서버 / 클라이언트에서 인코딩 / 디코딩을 위해 처리 오버 헤드를 추가합니다.
multipart/form-data
POST 에서 파일을 먼저 보내고 클라이언트에 ID를 반환하십시오. 그런 다음 클라이언트는 메타 데이터를 ID와 함께 보내고 서버는 파일과 메타 데이터를 다시 연결합니다.- 메타 데이터를 먼저 보내고 클라이언트에게 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에서 (업로드 된 파일 식별자)를 사용하지 않습니다 .
-
photo
서버에서 객체를 만듭니다 .POST: /projects/{project_id}/photos body: { name: "some_schema.jpg", comment: "blah"} response: photo_id
-
파일 업로드 (
file
사진 당 하나만 있기 때문에 단수형입니다) :POST: /projects/{project_id}/photos/{photo_id}/file body: file to upload response: -
그리고 예를 들어 :
-
사진 목록 읽기
GET: /projects/{project_id}/photos response: [ photo, photo, photo, ... ] (array of objects)
-
일부 사진 정보 읽기
GET: /projects/{project_id}/photos/{photo_id} response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
-
사진 파일 읽기
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);
}
답변
유일하게 누락 된 예제는 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();