[C#] 웹 서버로 HttpWebRequest를 이용하여 파일 업로드하는 방법


Development note/C#  2020. 6. 26. 13:51

안녕하세요. 명월입니다.


이 글은 C#에서 웹 서버로 HttpWebRequest를 이용하여 파일 업로드하는 방법에 대한 글입니다.


이전에 제가 MVC Framework로 파일 업로드하는 방법을 설명한 적이 있습니다.

링크 - [C#] MVC에서 파일 업로드하는 방법


여기서는 위 웹서버를 이용해서 파일 업로드를 하기는 하는데, 프로그램을 통해서 업로드하는 방법입니다. 우리가 웹 사이트에 파일 업로드를 해야하는데 종류가 많다고 할 때, 마우스 클릭으로 하나하나 클릭을 한다면 시간도 많이 걸리고 매우 불편할 것입니다.

그것 프로그램으로 일괄 업로드가 가능하다면 편하겠습니다.


서버측 소스입니다. (위 링크에서 그대로 가져왔습니다.)

@{
  // Layout을 사용하지 않음
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <title>FileUplaod</title>
</head>
<body>
  <div>
    <!-- 메시지를 표시하는 영역 -->
    @ViewBag.Message
  </div>
  <br />
  <!-- 파일 업로드 form enctype는 multipart/form-data는 메소드 타입은 POST, URL은 /Home/FileUpload이다. -->
  <form method="POST" enctype="multipart/form-data" action="/Home/FileUpload">
    <!-- file input 태그, 이름은 file, 복수 파일 첨부를 위해 multiple 속성을 추가한다. -->
    <input type="file" name="file" multiple/>
    <!-- submit, 즉 전송 -->
    <input type="submit">
  </form>
</body>
</html>
using System.IO;
using System.Web;
using System.Web.Mvc;
namespace FileUpload.Controllers
{
  // Home으로 시작하는 Controller
  public class HomeController : Controller
  {
    // 요청 URL - Home/Index
    public ActionResult Index()
    {
      // /View/Home/Index.cshtml를 렌더링한다.
      return View();
    }
    // 요청 URL - Home/FileUpload, 파라미터 요소를 생략한다.
    public ActionResult FileUpload()
    {
      try
      {
        // Request.Files에 데이터가 있는지 확인한다.
        if (Request.Files != null && Request.Files.Count > 0)
        {
          // 복수의 업로드를 하니깐 반복문으로...
          for (int i = 0; i < Request.Files.Count; i++)
          {
            // HttpPostedFileBase 인스턴스를 취득
            var file = Request.Files[i];
            // file이름 취득
            string filename = Path.GetFileName(file.FileName);
            // 저장할 경로 설정
            string savepath = Path.Combine(@"d:\work\", filename);
            // 파일 저장
            file.SaveAs(savepath);
          }
          // 메시지 설정
          ViewBag.Message = "File Uploaded Successfully.";
        }
        else
        {
          // 메시지 설정
          ViewBag.Message = "File upload failed";
        }
      }
      catch
      {
        // 메시지 설정
        ViewBag.Message = "File upload failed";
      }
      // /View/Home/Index.cshtml를 렌더링한다.
      return View("Index");
    }
  }
}

이제 HttpWebRequest로 콘솔에서 파일 업로드 프로그램을 만들기 전에 먼저 Http protocal에서 파일 업로드가 어떻게 올라가는 지 확인해야 합니다.

헤더를 보시면 content-type에 boundary가 설정되는 것을 확인할 수 있습니다.

이 바운더리는 파일별로 바이너리를 구분하는 구분자라고 생각하면됩니다.

; 해더 영역 (기본 설정으로 보내도 상관없는 부분입니다.)
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cache-control: max-age=0
origin: https://localhost:44387
referer: https://localhost:44387/
sec-ch-ua: "\\Not;A\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"
sec-ch-ua-mobile: ?0
sec-fetch-dest: document
sec-fetch-mode: navigate
sec-fetch-site: same-origin
sec-fetch-user: ?1
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4181.9 Safari/537.36
; content-type에 boundary 설정을 해야한다.
content-type: multipart/form-data; boundary=**boundaryline**
; 데이터 영역의 크기
content-length: 161848
; 헤더 영역 끝
; 데이터 영역 시작 앞에 --를 추가한다.
--**boundaryline**
Content-Disposition: form-data; name="nowonbuntistory.png"; filename="nowonbuntistory.png"
Content-Type: application/octet-stream

####바이너리#####
--**boundaryline**
Content-Disposition: form-data; name="nowonbun.png"; filename="nowonbun.png"
Content-Type: application/octet-stream

####바이너리#####
--**boundaryline**--
; 끝은 바운더리뒤에 --를 추가한다.

위의 프로토콜 형식으로 프로그램을 작성하겠습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Linq;

namespace Exception3
{
  class Program
  {
    // 파일 마다 바운더리 만드는 함수
    static byte[] ReadFileToBoundary(string filepath, string boundary)
    {
      // 시작 바운더리 설정
      var boundarybytes = Encoding.ASCII.GetBytes($"\r\n--{boundary}\r\n");
      // 파일 읽어오기
      FileInfo fileInfo = new FileInfo(filepath);
      // 바운더리 해더 작성, Content-Type를 설정해도 됩니다. image/jpg 등, 특별한 설정이 없으면 application/octet-stream로 설정
      var header = Encoding.ASCII.GetBytes($"Content-Disposition: form-data; name=\"{fileInfo.Name}\"; filename=\"{fileInfo.Name}\"\r\nContent-Type: application/octet-stream\r\n\r\n");
      // 메모리 스트림으로 작성
      using (Stream stream = new MemoryStream())
      {
        // 바운더리 작성
        stream.Write(boundarybytes, 0, boundarybytes.Length);
        // 해더 작성
        stream.Write(header, 0, header.Length);
        // 파일 스트림 취득
        using (Stream fileStream = fileInfo.OpenRead())
        {
          // 스트림 이동
          fileStream.CopyTo(stream);
        }
        // stream을 byte로 변환할 버퍼 생성
        var ret = new byte[stream.Length];
        // 스트림 seek 이동
        stream.Seek(0, SeekOrigin.Begin);
        // stream을 byte로 이동
        stream.Read(ret, 0, ret.Length);
        // byte 반환
        return ret;
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 바운더리 설정
      string boundary = "**boundaryline**";
      // 접속할 url로 HttpWebRequest 설정
      HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://localhost:44387/Home/FileUpload");
      // ContentType 설정
      request.ContentType = "multipart/form-data; boundary=" + boundary;
      // Method 설정
      request.Method = "POST";
      request.KeepAlive = true;
      // 파일을 읽어서 boundary 타입으로 변환하여 list로 설정
      var filelist = new List<byte[]>
      {
        ReadFileToBoundary(@"d:\\nowonbuntistory.png", boundary),
        ReadFileToBoundary(@"d:\\nowonbun.png", boundary),
      };
      // 종료 바운더리 설정
      var endboundary = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
      // 데이터 영역 크기 설정
      request.ContentLength = filelist.Sum(x => x.Length) + endboundary.Length;
      // 데이터 영역에 쓸 스트림 취득
      using (Stream stream = request.GetRequestStream())
      {
        // 파일 별 바운더리 설정
        foreach (var file in filelist)
        {
          // 스트림에 바운더리 작성
          stream.Write(file, 0, file.Length);
        }
        // 마지막 종료 바운더리 작성
        stream.Write(endboundary, 0, endboundary.Length);
      }
      // http 요청해서 결과를 받는다.
      using (var response = request.GetResponse())
      {
        // 결과 stream을 받아온다.
        using (Stream stream2 = response.GetResponseStream())
        {
          // StreamReader로 변환
          using (StreamReader reader2 = new StreamReader(stream2))
          {
            // 콘솔 출력
            Console.WriteLine(reader2.ReadToEnd());
          }
        }
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 소스를 실행하게 되면 html이 응답이 오게 됩니다.

결과 메시지 부분에 보면 File Upload Successfully라는 업로드가 완료된 것을 확인할 수 있습니다.

실제로 서버에서 파일을 제대로 받아서 파일로 저장을 했는지 확인이 되었습니다.


여기까지 C#에서 웹 서버로 HttpWebRequest를 이용하여 파일 업로드하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.