HttpWebRequest : 파일 전송하기


Intro

 제가 얼마전 삽실한 스토리입니다. =_=
프로젝트에서 네트워크를 통해 파일을 전송하는 모듈을 개발해야만 했습니다. 아시다시피 .NET 은 파일전송에 대해서 상당히 편리합니다. WebClient 를 사용하면 되거든요. 그런데 문제에 부딛히고야 말았습니다...


Content

파일을 받는 서버가 완벽한 표준을 지키지 않아서 그런지 WebClient.FileUpload() 를 통해 파일을 전송하면 데이타가 깨졌습니다. 엄밀히 말하자면 전송 완료된 파일이 원본보다 Size 가 컸습니다. 물론 정상적으로 파일을 읽을 수도 없었죠. 아마도 Boundary 에 대해서 서버가 비정상적으로 동작하는게 아닌가 싶습니다. 아직도 원인은 잘 모릅니다. 다른 side-effect 도 발생하여 가차없이 WebClient 를 포기했습니다. 원인 분석할 시간도 없었죠. 아시는 분은 코멘트를 달아 주세요~

 아무튼 그래서 직접 Stream 을 데이타를 Write 하기로 결정했습니다. 그리고 작업을 해 나갔죠. 그런데! 또 다시 문제가 발생하였죠. 200MB 이상 되는 파일의 경우 전송 중간에 연결이 끊어져 버리는 것이었습니다. -_-;
 코드는 요약하자면 아래와 같습니다. 여러분도 한번 원인을 찾아 보세요.



System.Net.HttpWebRequest webRequest = System.Net.HttpWebRequest.Create(task.DestURI) as System.Net.HttpWebRequest;
webRequest.Method = "POST";
webRequest.Accept = "*/*";
webRequest.UserAgent = "DLNADOC/1.50";
webRequest.Timeout = 10000;
webRequest.KeepAlive = true;
webRequest.SendChunked = true;
writeStream = webRequest.GetRequestStream();
long totalSize = new FileInfo(task.SourceURI.LocalPath).Length;
long totalUploadSize = 0; 
byte[] buffer = new byte[1024]; 
readStream = new FileStream(task.SourceURI.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read); 
int count = 0; 
while ((count = readStream.Read(buffer, 0, buffer.Length)) > 0) {
    writeStream.Write(buffer, 0, count);
    totalUploadSize += count;
} 


아시겠나요? 왜 200 MB 이상 되는 파일을 전송할 수 없는지를...

한참을 들여다보다, 문득 이런 생각이 들었습니다.
"문제는 200MB 가 아니라 '시간'이 아닐까?"

그리고 시간과 연관이 있는 "webRequest.Timeout = 10000;" 이 의심스러워 졌습니다. 제가 의도한 것은 WebStream 을 가져오는것에 대한 TimeOut 이었는데, 어쩌면 이게 StreamOpen ~ StreamClose 까지의 Time 일지도 모른다는 생각이 들었죠. 그래서 아래처럼 코드를 수정하였습니다.


webRequest.Timeout = System.Threading.Timeout.Infinite;


 아~~ 그리고 테스트를 했는데, 너무나 잘 되는 것입니다. 200 MB 가 문제가 아니고, 1 GB 도 잘 됩니다. 소위 웹질이라 불리는 행위를 시작한게 중학교 1학년 나이인 14살이었는데... 아.. 제 자신이 한심해 졌습니다.(컴퓨터를 처음 구입한건 그보다 7년 전입니다.)


 그렇게 프로젝트의 모듈개발은 잘 마무리 되었냐고요? 그럴리가요...
또 다시 문제는 찾아왔습니다! 파일 10개를 Queue 방식으로 하나씩 하나씩 전송을 하면 2~3 개 정도 전송되다가 나머지는 모두 Fail 해버리는 것입니다. 왜!! 2~3 개 밖에 전송이 안되지???


대략적인 코드는 아래와 같습니다. (가독성을 위해서 설명에 불필요한 try-catch 및 각종 이벤트는 제거 하였습니다.)


protected virtual void Transfer(object arg) {
    Stream writeStream = null;
    FileStream readStream = null;
    System.Net.HttpWebRequest webRequest = System.Net.HttpWebRequest.Create(task.DestURI) as System.Net.HttpWebRequest; 
    webRequest.Method = "POST";
    webRequest.Accept = "*/*";
    webRequest.UserAgent = "DLNADOC/1.50";
    webRequest.Timeout = System.Threading.Timeout.Infinite; 
    webRequest.KeepAlive = true; 
    webRequest.SendChunked = true; 
    writeStream = webRequest.GetRequestStream(); 
    readStream = new FileStream(task.SourceURI.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read);

    int count = 0;
    while ((count = readStream.Read(buffer, 0, buffer.Length)) > 0) {
        writeStream.Write(buffer, 0, count);
    } 
    if (readStream != null) {
        try { 
            readStream.Close();
            readStream.Dispose();
        } catch { } 
    }
    
    if (writeStream != null) {
        try { 
            writeStream.Close(); 
            writeStream.Dispose();
        } catch { } 
    } 
} 


 이런 형태이고, Transfer() 함수를 외부에서 10번 정도 부르는 것입니다. 비동기-동기에서 발생하는 문제는 아닙니다.(실제로는 이벤트를 받아서 Queue 에서 하나씩 꺼내며, TransferAsync() 함수입니다.)
 원인을 찾아보세요~ 왜! 2~3개 이후로 더 이상 전송이 안될까요?

설마설마 하다가 의심가는 코드를 다시 수정했고, 문제는 해결 되었습니다. 아,, Response 를 꼭 받아야 하는 거였나;;; 수정된 최종 코드는 아래와 같습니다.


protected virtual void Transfer(object arg) {
    Stream writeStream = null; 
    FileStream readStream = null; 
    System.Net.HttpWebRequest webRequest = System.Net.HttpWebRequest.Create(task.DestURI) as System.Net.HttpWebRequest; 
    webRequest.Method = "POST"; 
    webRequest.Accept = "*/*"; 
    webRequest.UserAgent = "DLNADOC/1.50";
    webRequest.Timeout = System.Threading.Timeout.Infinite; 
    webRequest.KeepAlive = true; 
    webRequest.SendChunked = true; 
    writeStream = webRequest.GetRequestStream(); 
    readStream = new FileStream(task.SourceURI.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read); 
    int count = 0; 
    while ((count = readStream.Read(buffer, 0, buffer.Length)) > 0) {
        writeStream.Write(buffer, 0, count);
    } 
    
    if (readStream != null) { 
        try { 
            readStream.Close(); 
            readStream.Dispose(); 
        } catch { } 
    }
    
    if (writeStream != null) { 
        try { 
            writeStream.Close(); 
            writeStream.Dispose(); 
        } catch { } 
    } 
    
    if (webRequest != null) { 
        try { 
            System.Net.WebResponse response = webRequest.GetResponse(); 
            response.Close(); 
        } catch { } 
    } 
}  



아하하하...아시겠죠? 휴..
삽질삽질...이런 삽질이 없네요. 여러분은 이런 삽질하지 않으시길 바래요...



Epliogue

 위에 코드는 설명을 위해서 간추려진 코드입니다. 사실 저렇게 개발하면 문제가 많죠. 네트워크인 만큼 예외처리도 다 해 주어야 하고, 비동기 함수이니까 이벤트도 달아 주어야 합니다. 그 외에 null 체크는 물론이거니와 신경써야 할 것들이 많죠. 아무튼 오해는 없으시기 바랍니다~ ^^