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


250

현재이 URL을 공개하는 REST 웹 서비스가 있습니다.

http : // server / data / media

사용자는 POST다음 JSON을 사용할 수 있습니다 .

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

새 미디어 메타 데이터를 만들려면

이제 미디어 메타 데이터와 동시에 파일을 업로드 할 수있는 기능이 필요합니다. 가장 좋은 방법은 무엇입니까? 새로운 속성을 도입 file하고 base64로 파일을 인코딩 할 수는 있지만 더 좋은 방법이 있는지 궁금합니다.

또한 사용하여 있습니다 multipart/form-dataHTML 양식을 보내 것이 무엇처럼,하지만 난 REST 웹 서비스를 사용하고 있는데 나는 경우 모든 가능한에서 JSON을 사용하여에 충실하고자합니다.


36
RESTful 웹 서비스를 사용하기 위해 JSON 만 사용하는 것이 실제로 필요한 것은 아닙니다. REST는 기본적으로 HTTP 메소드의 주요 원칙과 다른 (비 표준화되지 않은) 규칙을 따르는 것입니다.
Erik Kaplun

답변:


192

나는 2 단계 접근법이 합리적인 해결책이라는 Greg에 동의하지만, 다른 방법으로 할 것입니다. 난 그럴거야:

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

메타 데이터 항목을 작성하고 다음과 같은 응답을 리턴하려면 다음을 수행하십시오.

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

클라이언트는이 ContentUrl을 사용하고 파일 데이터로 PUT을 수행 할 수 있습니다.

이 방법의 좋은 점은 서버가 시작될 때 엄청난 양의 데이터로 무게가 줄어들 때 반환되는 URL이 더 많은 공간 / 용량을 가진 다른 서버를 가리킬 수 있다는 것입니다. 또는 대역폭이 문제인 경우 일종의 라운드 로빈 방식을 구현할 수 있습니다.


8
컨텐츠를 먼저 전송하는 것의 한 가지 장점은 메타 데이터가 존재할 때 컨텐츠가 이미 존재한다는 것입니다. 궁극적으로 정답은 시스템의 데이터 구성에 따라 다릅니다.
Greg Hewgill

고마워, 나는 이것이 내가하고 싶은 일이기 때문에 이것을 정답으로 표시했다. 불행하게도 이상한 비즈니스 규칙으로 인해 업로드가 임의 순서 (메타 데이터 우선 또는 파일 우선)로 발생하도록해야합니다. 두 상황을 다루는 두통을 줄이기 위해 두 가지를 결합 할 수있는 방법이 있는지 궁금했습니다.
Daniel T.

@Daniel 데이터 파일을 먼저 POST하면 Location에 반환 된 URL을 가져와 메타 데이터의 ContentUrl 특성에 추가 할 수 있습니다. 이렇게하면 서버가 메타 데이터를 수신 할 때 ContentUrl이 있으면 파일의 위치를 ​​이미 알고 있습니다. ContentUrl이 없으면 새로 작성해야한다는 것을 알게됩니다.
Darrel Miller

POST를 먼저 수행 한 경우 동일한 URL에 게시 하시겠습니까? (/ server / data / media) 또는 파일 우선 업로드를위한 다른 진입 점을 작성 하시겠습니까?
Matt Brailsford

1
@Faraway 메타 데이터에 이미지의 "좋아요"수가 포함 된 경우 어떻게합니까? 그렇다면 단일 리소스로 취급 하시겠습니까? 또는 이미지의 설명을 편집하려면 이미지를 다시 업로드해야한다고 제안하고 있습니까? 여러 부분으로 구성된 양식이 올바른 솔루션 인 경우가 많습니다. 항상 그런 것은 아닙니다.
Darrel Miller

104

전체 요청 본문을 JSON으로 래핑하지 않기 때문에 multipart/form-data단일 요청으로 JSON과 파일을 모두 게시하는 데 사용 하는 것이 충분하지 않다는 의미는 아닙니다 .

curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file

서버 측에서 (의사 코드에 Python 사용) :

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

여러 파일을 업로드하려면 각각에 대해 별도의 "양식 필드"를 사용할 수 있습니다.

curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file

...이 경우 서버 코드는 것 request.args['file1'][0]request.args['file2'][0]

또는 많은 사람들에게 동일한 것을 재사용하십시오 :

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz" -F "files=@some-other-file.tar.gz" http://example.com/add-file

...이 경우 request.args['files']단순히 길이 2의 목록이됩니다.

또는 단일 필드를 통해 여러 파일을 전달하십시오.

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz,some-other-file.tar.gz" http://example.com/add-file

...이 경우 request.args['files']모든 파일을 포함하는 문자열이 될 것이며, 자신을 파싱해야합니다. 어떻게해야할지 확실하지 않지만, 어렵지 않거나 이전 방법을 사용하는 것이 좋습니다.

의 차이 @와는 <@, 반면에 파일이 업로드 파일로 첨부하려면 원인 <텍스트 필드로 파일의 첨부합니다 내용.

추신 : 요청 curl을 생성하는 방법으로 사용 POST한다고해서 정확히 같은 HTTP 요청을 Python과 같은 프로그래밍 언어에서 보내거나 충분한 기능을 갖춘 도구를 사용하여 전송할 수는 없습니다.


4
나는이 접근 방식에 대해 궁금해했고, 왜 다른 사람이 아직 그것을 보지 못했는지를 궁금해했다. 나는 나에게 완벽하게 편안한 것처럼 보인다.
soupdog

1
예! 이것은 매우 실용적인 접근 방식이며 "application / json"을 전체 요청의 컨텐츠 유형으로 사용하는 것보다 RESTful하지 않습니다.
sickill

..하지만 .json 파일에 데이터가 있고 업로드하지 않은 경우에만 가능합니다.
itsjavi

5
@mjolnic 귀하의 의견은 중요하지 않습니다. cURL 예제는 단지 예입니다 . 대답은 명시 적으로 요청을 보내는 데 무엇이든 사용할 수 있다고 명시하고 있습니다 curl -f 'metadata={"foo": "bar"}'.
Erik Kaplun

3
승인 된 답변이 개발중인 응용 프로그램에서 작동하지 않기 때문에이 접근법을 사용하고 있습니다 (파일은 데이터 전에 존재할 수 없으며 데이터가 먼저 업로드되고 파일이 업로드되지 않는 경우를 처리하기 위해 불필요한 복잡성을 추가합니다) .
BitsEvolved

33

문제에 접근하는 한 가지 방법은 업로드를 2 단계 프로세스로 만드는 것입니다. 먼저 POST를 사용하여 파일 자체를 업로드합니다. 여기서 서버는 일부 식별자를 클라이언트에 반환합니다 (식별자는 파일 내용의 SHA1 일 수 있음). 그런 다음 두 번째 요청은 메타 데이터를 파일 데이터와 연관시킵니다.

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

JSON 요청 자체로 인코딩 된 파일 데이터베이스 base64를 포함하면 전송되는 데이터 크기가 33 % 증가합니다. 파일의 전체 크기에 따라 중요하거나 중요하지 않을 수 있습니다.

다른 방법은 원시 파일 데이터의 POST를 사용하지만 HTTP 요청 헤더에 메타 데이터를 포함시키는 것입니다. 그러나 이것은 기본 REST 작업과 약간 다르며 일부 HTTP 클라이언트 라이브러리에서는 더 어색 할 수 있습니다.


Ascii85는 1/4 만 증가시켜 사용할 수 있습니다.
Singagirl

왜 base64가 그 크기를 크게 증가 시키는가?
jam01

1
@ jam01 : 우연히, 어제 공간 질문에 잘 맞는 무언가를 보았습니다. Base64 인코딩의 공간 오버 헤드는 무엇입니까?
Greg Hewgill

10

나는 이것이 매우 오래된 질문이라는 것을 알고 있지만, 내가이 게시물을 방문했을 때 같은 것을 찾는 다른 누군가를 도울 수 있기를 바랍니다. 메타 데이터가 Guid and int라는 비슷한 문제가있었습니다. 솔루션은 동일합니다. 필요한 메타 데이터를 URL의 일부로 만들 수 있습니다.

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

그런 다음 경로를 등록하는 모든 경우 에이 경우 WebApiConfig.Register (HttpConfiguration config)가 필요합니다.

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

6

파일과 메타 데이터가 하나의 리소스를 만드는 경우 한 번의 요청으로 둘 다 업로드해도 좋습니다. 샘플 요청은 다음과 같습니다.

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\
)%!Y0i@.k
[
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--

3

8 년 동안 왜 아무도 쉬운 답변을 게시하지 않았는지 이해가되지 않습니다. 파일을 base64로 인코딩하는 대신 json을 문자열로 인코딩하십시오. 그런 다음 서버 측에서 json을 디코딩하십시오.

자바 스크립트에서 :

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

Content-Type을 사용하여 POST : multipart / form-data

서버 측에서 파일을 정상적으로 검색하고 json을 문자열로 검색하십시오. 문자열을 객체로 변환하십시오. 일반적으로 사용하는 프로그래밍 언어에 관계없이 한 줄의 코드입니다.

(예, 잘 작동합니다. 내 앱 중 하나에서 수행하고 있습니다.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.