HTTP 파일 업로드는 어떻게 작동합니까?


528

파일이 첨부 된 간단한 양식을 제출하면 :

<form enctype="multipart/form-data" action="http://localhost:3000/upload?upload_progress_id=12344" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>

파일을 내부적으로 어떻게 보냅니 까? 파일이 HTTP 본문의 일부로 데이터로 전송됩니까? 이 요청의 헤더에는 파일 이름과 관련된 내용이 없습니다.

파일을 보낼 때 HTTP의 내부 작동을 알고 싶습니다.


나는 스니퍼를 한동안 사용하지 않았지만 요청에 전송 된 것을보고 싶다면 (서버이므로 요청이므로) 스니핑하십시오. 이 질문은 너무 광범위합니다. 따라서 특정 프로그래밍 질문에 더 적합합니다.
paparazzo

... 스니퍼가 갈수록 피들러 는 내가 선택한 무기입니다. 자체 테스트 요청을 작성하여 게시 방법을 확인할 수도 있습니다.
Phil Cooper

관심이 있으신 분들 MAX_FILE_SIZEstackoverflow.com/q/1381364/632951의
PHP-

MAX_FILE_SIZE가 이상하다고 생각합니다. 크롬에서 HTML을 100000000으로 수정하여 게시하기 전에 더 나은 가치를 게시 할 수 있습니다. 1. 쿠키를 소금을 통해 안전한 해시와 함께 쿠키에 넣으십시오. 쿠키를 수정하면 서버는 웹 피스 또는 플레이 프레임 워크와 같이 예외를 확인하고 던질 수 있습니다. @ 0xSina
딘 힐러

답변:


320

파일을 선택하고 양식을 제출할 때 어떤 일이 발생하는지 살펴 보겠습니다 (간단하게 헤더를 자른 것입니다).

POST /upload?upload_progress_id=12344 HTTP/1.1
Host: localhost:3000
Content-Length: 1325
Origin: http://localhost:3000
... other headers ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L

------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"

100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="uploadedfile"; filename="hello.o"
Content-Type: application/x-object

... contents of file goes here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--

참고 : --마지막 경계 문자열의 끝에서와 같이 각 경계 문자열에는 접두사가 추가되어야 합니다. 위의 예는 이미 이것을 포함하고 있지만 놓치기 쉽습니다. 아래 @Andreas의 코멘트를 참조하십시오.

양식 매개 변수를 URL 인코딩하는 대신 양식 매개 변수 (파일 데이터 포함)는 요청 본문의 여러 부분으로 된 문서에서 섹션으로 전송됩니다.

위의 예에서는 MAX_FILE_SIZE파일 데이터가 포함 된 섹션뿐만 아니라 양식에 설정된 값으로 입력 을 볼 수 있습니다 . 파일 이름은 Content-Disposition헤더의 일부입니다 .

자세한 내용은 여기에 있습니다 .


7
@ source.rar : 아니요. 웹 서버는 항상 연결되어 동시 연결을 처리 할 수 ​​있습니다. 기본적으로 포트 80에서 수신 대기중인 디먼 프로세스는 다른 연결 수신 대기로 돌아 가기 위해 다른 스레드 / 프로세스에 제공하는 태스크를 즉시 처리합니다. 두 개의 들어오는 연결이 정확히 같은 순간에 도착하더라도 데몬이 읽을 준비가 될 때까지 네트워크 버퍼에 앉아 있습니다.
eggyal

10
스레딩 설명은 단일 스레드로 설계되고 상태 머신을 사용하여 연결에서 데이터 패킷을 빠르게 다운로드하기 때문에 고성능 서버가 있으므로 약간 잘못되었습니다. 오히려 TCP / IP에서 포트 80은 데이터가 전송되는 포트가 아니라 수신 포트입니다.
slebetman

9
IP 수신 소켓 (포트 80)이 연결을 수신하면 일반적으로 1000보다 큰 임의의 숫자로 다른 포트에 다른 소켓이 작성됩니다.이 소켓은 원격 소켓에 연결되어 포트 80을 새 연결을 수신 대기합니다.
slebetman

11
@slebetman 우선, 이것은 HTTP에 관한 것입니다. FTP 활성 모드는 여기에 적용되지 않습니다. 둘째, 모든 연결에서 청취 소켓이 차단되는 것은 아닙니다. 다른쪽에는 자신의 끝을 바인딩 할 포트가 있으므로 한 포트에 대해 많은 연결을 가질 수 있습니다.
Slotos

33
Content-Type 헤더 필드의 일부로 전달되는 경계 문자열은 아래의 개별 부분에 대한 경계 문자열보다 2 자 짧습니다. 방금 첫 번째 경계 문자열에는 4 개의 대시가 있지만 다른 경계 문자열에는 6 개의 대시가 있음을 알기 어렵 기 때문에 업 로더가 작동하지 않는 이유를 알아 내려고 한 시간을 보냈습니다. 즉, 경계 문자열을 사용하여 개별 양식 데이터를 분리 할 때는 접두사 두 개로 시작해야합니다.-물론 RFC1867에 설명되어 있지만 여기서도 지적해야한다고 생각합니다.
Andreas

279

파일을 내부적으로 어떻게 보냅니 까?

형식은 호출 multipart/form-data에서 요청과 같은 : 무엇에 enctype = '다중 / 폼 데이터'는 뜻입니까?

할거다:

  • HTML5 참조를 더 추가하십시오.
  • 설명 그가 양식을 제출 예와 권리

HTML5 참조

다음과 같은 세 가지 가능성 이 있습니다 enctype.

예제를 생성하는 방법

각 방법의 예를 보면 각 방법의 작동 방식과 사용 방법이 명확 해집니다.

다음을 사용하여 예제를 생성 할 수 있습니다.

양식을 최소 .html파일 로 저장하십시오 .

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>upload</title>
</head>
<body>
  <form action="http://localhost:8000" method="post" enctype="multipart/form-data">
  <p><input type="text" name="text1" value="text default">
  <p><input type="text" name="text2" value="a&#x03C9;b">
  <p><input type="file" name="file1">
  <p><input type="file" name="file2">
  <p><input type="file" name="file3">
  <p><button type="submit">Submit</button>
</form>
</body>
</html>

우리의 기본 텍스트 값 설정 a&#x03C9;b수단 aωb때문 ω이다 U+03C9바이트있는, 61 CF 89 62UTF-8인치

업로드 할 파일을 작성하십시오.

echo 'Content of a.txt.' > a.txt

echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html

# Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
printf 'a\xCF\x89b' > binary

작은 에코 서버를 실행하십시오.

while true; do printf '' | nc -l 8000 localhost; done

브라우저에서 HTML을 열고 파일을 선택한 후 제출을 클릭하고 터미널을 확인하십시오.

nc 받은 요청을 인쇄합니다.

테스트 대상 : Ubuntu 14.04.3, ncBSD 1.105, Firefox 40.

멀티 파트 / 폼 데이터

Firefox가 보냈습니다 :

POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 834

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text1"

text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text2"

aωb
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain

Content of a.txt.

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream

aωb
-----------------------------735323031399963166993862150--

이진 파일 및 텍스트 필드의 경우 바이트 61 CF 89 62( aωbUTF-8)가 문자 그대로 전송됩니다. nc -l localhost 8000 | hd를 사용하여 바이트를 확인할 수 있습니다 .

61 CF 89 62

전송되었습니다 ( 61== 'a'및 62== 'b').

따라서 다음 사항이 분명합니다.

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150컨텐츠 유형을로 설정하고 multipart/form-data필드가 주어진 boundary문자열 로 구분된다고 말합니다 .

    그러나 다음 사항에 유의하십시오.

    boundary=---------------------------735323031399963166993862150
    

    --실제 장벽보다 덜 두껍 습니다.

    -----------------------------735323031399963166993862150
    

    표준에 경계가 두 개의 대시로 시작해야하기 때문 --입니다. 다른 대시는 Firefox가 임의의 경계를 구현하기로 선택한 방식으로 보입니다. RFC 7578은 다음 두 개의 대시 --가 필요하다고 분명히 언급 합니다.

    4.1. multipart / form-data의 "경계"매개 변수

    다른 다중 부품 유형과 마찬가지로 부품은 CRLF, "-"및 "boundary"매개 변수의 값을 사용하여 구성된 경계 구분 기호로 구분됩니다.

  • 모든 필드는 데이터 전에 일부 서브 헤더를 가져옵니다 Content-Disposition: form-data;필드 namefilename데이터 다음을.

    서버는 다음 경계 문자열까지 데이터를 읽습니다. 브라우저는 어떤 필드에도 나타나지 않는 경계를 선택해야하므로 요청마다 경계가 다를 수 있습니다.

    고유 경계가 있으므로 데이터 인코딩이 필요하지 않습니다. 이진 데이터는 그대로 전송됩니다.

    TODO : 최적의 경계 크기 ( log(N)내기)와 그것을 찾는 알고리즘의 이름 / 실행 시간은 얼마입니까? 질문 : /cs/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type 브라우저에 의해 자동으로 결정됩니다.

    정확하게 결정된 방법 : 업로드 된 파일의 MIME 유형은 브라우저에 의해 어떻게 결정됩니까?

application / x-www-form-urlencoded

이제 변경 enctype에를 application/x-www-form-urlencoded, 브라우저, 그리고 다시 보내기를 다시로드합니다.

Firefox가 보냈습니다 :

POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: application/x-www-form-urlencoded
Content-Length: 51

text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary

분명히 파일 데이터는 전송되지 않았고 기본 이름 만 전송되었습니다. 따라서 파일에는 사용할 수 없습니다.

텍스트 필드에 관해서는, 우리는 같은 것을 보통의 인쇄 가능한 문자를 참조 ab같은 인쇄 할 수없는 것들 동안 1 바이트에 보내졌다 0xCF하고 0x89위로했다 3 바이트 각 : %CF%89!

비교

파일 업로드는 종종 인쇄 할 수없는 많은 문자 (예 : 이미지)를 포함하지만 텍스트 형식은 거의 없습니다.

예제에서 우리는 다음을 보았습니다.

  • multipart/form-data: 메시지에 몇 바이트의 경계 오버 헤드를 추가하고 계산하는 데 시간이 걸리지 만 각 바이트를 1 바이트로 보냅니다.

  • application/x-www-form-urlencoded: 필드 당 단일 바이트 경계 ( &)를 갖지만 인쇄 할 수없는 모든 문자에 대해 3 배선형 오버 헤드 계수를 추가합니다 .

따라서로 파일을 보낼 수 있어도 application/x-www-form-urlencoded비효율적이므로 원하지 않습니다.

그러나 텍스트 필드에있는 인쇄 가능한 문자의 경우 중요하지 않으며 오버 헤드가 적으므로 그냥 사용하십시오.


1
이진 첨부 파일을 어떻게 추가 하시겠습니까? (작은 이미지)- Content-DispositionContent-Type속성 의 값이 변경되는 것을 볼 수 있지만 '콘텐츠'를 처리하는 방법은 무엇입니까?
blurfus

3
@ianbeks 브라우저는 요청을 보내기 전에 자동으로 브라우저를 수행합니다. 어떤 휴리스틱을 사용하는지 모르지만 파일 확장자가 그중 하나 일 가능성이 큽니다. 이 질문에 대답 할 수 있습니다 stackoverflow.com/questions/1201945/...
치로 틸리을郝海东冠状病六四事件法轮功

3
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视이 답변이 선택한 답변보다 훨씬 낫다고 생각합니다. 그러나 프로필에서 관련없는 콘텐츠를 제거하십시오. 그것은 SO의 정신에 위배됩니다.
smwikipedia

2
@smwikipedia rfc 견적과이 답변을 좋아해 주셔서 감사합니다! 사용자 이름 : 저에게있어 SO의 정신은 모든 사람이 항상 최상의 정보를 가져야한다는 것입니다. ~~이 토론을 트위터 나 메타에 맡기겠습니다. 평화.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@KumarHarsh는 대답하기에 충분한 세부 사항이 아니라고 생각합니다. 새로운 매우 상세한 질문을여십시오.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

62

이진 콘텐츠로 파일 보내기 (양식 또는 FormData없이 업로드)

주어진 답변 / 예에서 파일은 HTML 양식으로 또는 FormData API를 사용하여 업로드 될 가능성이 높습니다 . 파일은 요청에서 전송 된 데이터의 일부일 뿐이므로 multipart/form-data Content-Type헤더입니다.

파일을 유일한 컨텐츠로 보내려면 요청 본문으로 직접 추가하고 Content-Type헤더를 전송중인 파일의 MIME 유형으로 설정할 수 있습니다. Content-Disposition헤더 에 파일 이름을 추가 할 수 있습니다 . 다음과 같이 업로드 할 수 있습니다.

var xmlHttpRequest = new XMLHttpRequest();

var file = ...file handle...
var fileName = ...file name...
var target = ...target...
var mimeType = ...mime type...

xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);

양식을 사용하지 않으려는 경우 하나의 단일 파일 만 업로드하려는 경우 요청에 파일을 포함시키는 가장 쉬운 방법입니다.


Asp.Net 4.0으로이를 위해 서버 측 서비스를 어떻게 구성합니까? userId, path, captionText 등과 같은 여러 입력 매개 변수도 처리합니까?
Asle G

1
@AsleG 아니요, 요청 내용으로 단일 파일을 보내는 데만 사용됩니다. 저는 Asp.Net 전문가는 아니지만 요청에서 내용 (블롭)을 가져 와서 Content-Type헤더에서 파일로 저장해야합니다 .
Wilt

@AsleG 아마이 링크 가 도움 될 수 있습니다
Wilt

@wilt form을 사용하지 않지만 formdata API를 사용하고 싶은 경우 그렇게 할 수 있습니까?
화난 키위

1
@AnkitKhettry 양식이나 양식 API를 사용하여 업로드 한 것처럼 들립니다. 이 '이상한 문자열'은 일반적으로 양식 데이터를 서버의 부분으로 분리하는 데 사용되는 양식 경계입니다.
Wilt

9

이 샘플 Java 코드가 있습니다.

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class TestClass {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(8081);
        Socket accept = socket.accept();
        InputStream inputStream = accept.getInputStream();

        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        char readChar;
        while ((readChar = (char) inputStreamReader.read()) != -1) {
            System.out.print(readChar);
        }

        inputStream.close();
        accept.close();
        System.exit(1);
    }
}

이 test.html 파일이 있습니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>File Upload!</title>
</head>
<body>
<form method="post" action="http://localhost:8081" enctype="multipart/form-data">
    <input type="file" name="file" id="file">
    <input type="submit">
</form>
</body>
</html>

마지막으로 테스트 목적으로 사용할 파일 a.dat 라는 이름 은 다음과 같습니다.

0x39 0x69 0x65

위의 바이트를 ASCII 또는 UTF-8 문자로 해석하면 실제로 다음을 나타냅니다.

9ie

Java 코드를 실행하고 즐겨 사용하는 브라우저에서 test.html 을 열고 a.dat양식을 업로드 하고 제출 한 다음 서버가받는 내용을 확인하십시오.

POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary06f6g54NVbSieT6y
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: en,en-US;q=0.8,tr;q=0.6
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF

------WebKitFormBoundary06f6g54NVbSieT6y
Content-Disposition: form-data; name="file"; filename="a.dat"
Content-Type: application/octet-stream

9ie
------WebKitFormBoundary06f6g54NVbSieT6y--

Java에 문자를 UTF-8 문자로 처리하도록 인쇄하도록 지시했기 때문에 문자 9 를 보는 것에 놀라지 않습니다 . 원시 바이트로 읽도록 선택할 수도 있습니다.

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

실제로 마지막 HTTP 헤더입니다. 그 후 우리가 업로드 한 파일의 메타와 내용을 볼 수있는 HTTP 본문이 나온다.


6

HTTP 메시지에는 헤더 행 다음에 전송 된 데이터 본문이있을 수 있습니다. 이에 대한 응답으로 요청 된 리소스가 클라이언트 (메시지 본문의 가장 일반적인 용도)로 반환되거나 오류가있는 경우 설명 텍스트가 반환됩니다. 요청시 사용자가 입력 한 데이터 또는 업로드 된 파일이 서버로 전송됩니다.

http://www.tutorialspoint.com/http/http_messages.htm

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