2011-04-21 11 views
17

이 문제에 대해 많은 게시물을 찾았지만 브라우저에서 node.js 서버로 파일을 업로드하는 방법을 모두 언급했습니다. node.js 코드에서 다른 서버로 파일을 업로드하고 싶습니다. 내가 node.js에 대한 제한된 지식을 기반으로 작성하려고 시도했지만 작동하지 않습니다.node.js에서 파일을 업로드하는 방법

function (data) { 
    var reqdata = 'file='+data; 
    var request = http.request({ 
    host : HOST_NAME, 
    port : HOST_PORT, 
    path : PATH, 
    method : 'POST', 
    headers : { 
     'Content-Type' : 'multipart/form-data', 
     'Content-Length' : reqdata.length 
    } 
    }, function (response) { 
     var data = ''; 
     response.on('data', function(chunk) { 
     data += chunk.toString(); 
     }); 
     response.on('end', function() { 
     console.log(data); 
     }); 
    }); 

    request.write(reqdata+'\r\n\r\n'); 
    request.end(); 
}) 

위의 함수는 데이터를 생성하는 다른 코드에 의해 호출됩니다.

나는 curl -F "file = @ < 파일 경로 >"을 사용하여 동일한 데이터 파일을 업로드하려고했으나 업로드가 성공적으로 수행되었습니다. 하지만 내 코드가 실패합니다. 서버는 업로드 된 파일이 유효하지 않거나 손상되었음을 암시하는 응용 프로그램 특정 오류를 반환합니다.

나는 tcpdump 데이터를 수집하고이를 wireshark로 분석했다. 내 node.js 코드에서 보낸 패킷에 다중 부분 데이터에 필요한 경계가 없습니다. 나는 어떻게 Node.js를 코드에서이 작업을 수행 할 수

The multipart dissector could not find the required boundary parameter. 

어떤 생각 Wireshark는 패킷이 메시지가 표시?

답변

7

당신이 클라이언트는 일반적으로 "다중/폼 데이터를"처리하는 방법처럼 보이게하려는 경우 여러 부분이 꽤 복잡합니다, 당신은 몇 가지 일을해야합니다. 먼저 경계 키를 선택해야합니다.이 키는 일반적으로 부분의 시작과 끝을 표시하는 임의의 문자열입니다 (이 경우 단일 파일을 보내려고하기 때문에 한 부분 일 것입니다). 각 부분 (또는 한 부분)에는 컨텐트 유형, 양식 필드 이름 및 전송 인코딩을 설정하는 머리글 (경계 키로 초기화 됨)이 필요합니다. 부품이 완성되면 각 부품의 끝을 경계 키로 표시해야합니다.

저는 멀티 파트로 작업 한 적이 없지만 어떻게 할 수 있었는지 생각합니다.

var boundaryKey = Math.random().toString(16); // random string 
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"'); 
// the header for the one and only part (need to use CRLF here) 
request.write( 
    '--' + boundaryKey + '\r\n' 
    // use your file's mime type here, if known 
    + 'Content-Type: application/octet-stream\r\n' 
    // "name" is the name of the form field 
    // "filename" is the name of the original file 
    + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n' 
    + 'Content-Transfer-Encoding: binary\r\n\r\n' 
); 
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 }) 
    // set "end" to false in the options so .end() isnt called on the request 
    .pipe(request, { end: false }) // maybe write directly to the socket here? 
    .on('end', function() { 
    // mark the end of the one and only part 
    request.end('--' + boundaryKey + '--'); 
    }); 

가 다시 말하지만, 내가 전에이 해본 적이 없다,하지만 난 그것을 수행 할 수있는 방법입니다 생각 : 내가 틀렸다면 누군가 날 수정하시기 바랍니다. 어쩌면 지식이 풍부한 사람이 더 많은 통찰력을 제공 할 수 있습니다.

원시 바이너리가 아닌 base64 또는 인코딩으로 보내려면 모든 파이프 작업을 직접 수행해야합니다. 읽기 스트림을 일시 중지하고 요청시 드레인 이벤트를 기다려야 모든 메모리를 다 사용하지 않아야하기 때문에 더 복잡해질 것입니다 (큰 파일이 아닌 경우 일반적으로 그래도 이것에 대해 걱정할 필요는 없습니다).편집 : 실제로, 당신은 그냥 읽기 스트림 옵션에서 인코딩을 설정할 수 있습니다.

이미 노드 모듈이없는 경우 놀랄 것입니다. 어쩌면 그 주제에 대해 더 많은 정보를 얻은 누군가가 저수준의 세부 사항을 도울 수 있을지 모르지만, 나는 이것을 수행하는 어딘가에 모듈이 있어야한다고 생각합니다.

+0

나는 비슷한 것을 시도 해왔다. 오늘 나중에 제안을 드리겠습니다. 이 작업을 쉽게하기 위해이 노드 모듈을 발견했습니다. https://github.com/mikeal/request 그러나 아직 내 목적을 위해 잘 작동하지 않았습니다. – Jayesh

3

오류 메시지에 경계 매개 변수가 누락되었다고되어 있습니다. 임의의 문자열을 추가하여 각 파일을 나머지 파일/form-data와 구분해야합니다. 여기

는 요청이 같이 수있는 방법입니다 :

내용을 입력합니다

Content-Type:multipart/form-data; boundary=----randomstring1337 

몸 :

------randomstring1337 
Content-Disposition: form-data; name="file"; filename="thefile.txt" 
Content-Type: application/octet-stream 

[data goes here] 

------randomstring1337-- 

참고 그 시작과의 끝에서 -- 신체의 임의의 문자열이 중요합니다. 그것들은 프로토콜의 일부입니다.

더 많은 정보는 여기 http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

+0

당신이 최고입니다. –

12

jhcc's answer이 거의 있습니다.

테스트에서이 문제에 대한 지원이 필요함에 따라 약간 수정했습니다.

여기에 우리를 위해 작동하는 수정 된 버전입니다 :

var boundaryKey = Math.random().toString(16); // random string 
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"'); 
// the header for the one and only part (need to use CRLF here) 
request.write( 
    '--' + boundaryKey + '\r\n' 
    // use your file's mime type here, if known 
    + 'Content-Type: application/octet-stream\r\n' 
    // "name" is the name of the form field 
    // "filename" is the name of the original file 
    + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n' 
    + 'Content-Transfer-Encoding: binary\r\n\r\n' 
); 
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 }) 
    .on('end', function() { 
    // mark the end of the one and only part 
    request.end('\r\n--' + boundaryKey + '--'); 
    }) 
    // set "end" to false in the options so .end() isn't called on the request 
    .pipe(request, { end: false }) // maybe write directly to the socket here? 

변경 사항은 다음과 같습니다

  • ReadableStream.pipereturns the piped-to stream, 그래서 end 그에 호출되지 없구요. 대신 파일 읽기 스트림에서 end을 기다립니다.
  • request.end은 새 행에 경계를 설정합니다.
+4

이것은 나에게 효과적 이었지만 Content-Length 헤더를 추가해야 파일의 fs.stat를 호출하여 크기를 가져오고 전송 된 다른 모든 문자열 데이터의 Buffer.bytesLength에 추가해야했습니다. 경계 키부터 시작하여 전체 내용 길이를 파일 크기를 포함한 최종 '-'까지 줄이는 것과 같습니다. –

+0

이 방법으로 여러 부분을 보내고 싶습니다. 어떻게 달성 할 수 있습니까? 나는 각 부분 다음에 경계 분리 기호를 추가하여 추가하려고했지만 나에게 효과가있는 것 같지 않다. –

+0

@BjornTipling에는'Content-Disposition' 헤더와 구분 기호가 포함 되었습니까? 나는 그것이 그것들을 포함한 전신의 길이라고 기대할 것입니다. –

1

내가 할 수있는 가장 빠른 방법은 request 패키지를 사용하는 것입니다. 코드는 잘 문서화되었으며 방금 작동했습니다.

는 (내 테스트를 위해 내가 JSON 결과 및 비 엄격한 SSL을 원했다 - 많은 다른 옵션이 있습니다 ...)

var url = "http://"; //you get the idea 
var filePath = "/Users/me/Documents/file.csv"; //absolute path created elsewhere 
var r = request.post({ 
    url: url, 
    json: true, 
    strictSSL: false 
}, function(err, res, data) { 
    //console.log("Finished uploading a file"); 
    expect(err).to.not.be.ok(); 
    expect(data).to.be.ok(); 
    //callback(); //mine was an async test 
}); 
var form = r.form(); 
form.append('csv', fs.createReadStream(filePath)); 
관련 문제