[C#] 웹 서버로 HttpWebRequest를 이용하여 파일 업로드하는 방법
안녕하세요. 명월입니다.
이 글은 C#에서 웹 서버로 HttpWebRequest를 이용하여 파일 업로드하는 방법에 대한 글입니다.
이전에 제가 MVC Framework로 파일 업로드하는 방법을 설명한 적이 있습니다.
여기서는 위 웹서버를 이용해서 파일 업로드를 하기는 하는데, 프로그램을 통해서 업로드하는 방법입니다. 우리가 웹 사이트에 파일 업로드를 해야하는데 종류가 많다고 할 때, 마우스 클릭으로 하나하나 클릭을 한다면 시간도 많이 걸리고 매우 불편할 것입니다.
그것 프로그램으로 일괄 업로드가 가능하다면 편하겠습니다.
서버측 소스입니다. (위 링크에서 그대로 가져왔습니다.)
@{
// 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를 이용하여 파일 업로드하는 방법에 대한 글이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.