파일 업로드/다운로드 서버 (HttpServer)
파일 업로드/다운로드 서버 — multipart 해부와 JDK 내장 HttpServer
브라우저가 파일을 보낼 때 실제로 어떤 바이트가 네트워크를 지나가는지(multipart/form-data)를 해부하고, 외부 라이브러리 없이 JDK 의
HttpServer와HttpClient만으로 "업로드 → 검증 → 저장 → 목록 → 다운로드(한글 파일명, Range)" 서버를 만들어 Spring 의MultipartFile이 뒤에서 무엇을 대신해 주는지 이해합니다.
1. 왜 배우는가
"회원 명단 엑셀을 올리면 일괄 등록되게 해 주세요", "정산 결과를 엑셀로 내려받게 해 주세요". 실무에서 파일 업로드/다운로드가 없는 시스템은 거의 없습니다. Spring 을 쓰면 @RequestParam MultipartFile file 한 줄로 끝나는 것처럼 보이지만, 그 한 줄 뒤에서 벌어지는 일을 모르면 다음과 같은 사고를 만납니다.
10MB 제한을 걸었는데 서버가 OOM 으로 죽는다(전체를 메모리에 올렸다). 다운로드한 파일 이름이 %ED%9A%8C... 로 깨진다(Content-Disposition 인코딩). image.png 라고 올렸는데 사실은 실행 파일이었다(확장자만 믿었다). 사용자가 보낸 파일명 ../../etc/passwd 가 그대로 저장 경로가 되었다(경로 탐색). 동영상 다운로드가 중간에 끊기면 처음부터 다시 받는다(Range 미지원).
이 레슨은 프레임워크를 걷어내고 프로토콜 수준에서 시작합니다. HTTP 요청 본문에 파일이 어떤 형태로 실리는지 바이트 단위로 보고, 그것을 메모리에 다 올리지 않고 파싱하는 스트리밍 파서를 직접 만들고, 신뢰 경계(사용자 입력)에서 해야 할 검증을 한곳에 모으고, 다운로드 응답 헤더를 규격대로 조립합니다.
서버는 JDK 에 내장된 com.sun.net.httpserver.HttpServer 를 쓰므로 의존성이 하나도 없고, java Main 으로 바로 브라우저에서 확인할 수 있습니다.
직접 만든 파서를 실무에서 쓰라는 뜻은 아닙니다. Tomcat 과 Spring 이 같은 일을 훨씬 견고하게 해 줍니다.
그러나 MultipartFile.getInputStream() 이 왜 스트림인지, spring.servlet.multipart.max-file-size 가 어느 단계에서 걸리는지, Content-Disposition 에 왜 filename 과 filename* 을 둘 다 넣는지를 원리로 알면 설정 하나, 헤더 한 줄을 "검색해서 복붙" 하지 않고 이유를 알고 쓰게 됩니다.
마지막 절의 Servlet/Spring 대응표가 그 다리입니다.