HTTP 에서 평문 텍스트 뿐만 아니라 파일을 전달해야 할 수 있습니다.

서버 -> 클라이언트 파일 전달 (다운로드)

핸들러 함수에서 제공

func handleFunc(c *gin.Context) {
	// file path in server
	c.File("cat.PNG")
}

서버에 있는 파일을 전달하는 간단한 방법 입니다. 제공 할 서버내 파일 경로를 인자로 전달해 주면 됩니다.

var fs http.FileSystem = http.Dir("./")

func handleFunc(c *gin.Context) {
	c.FileFromFS("cat.PNG", fs)
}

Go 1.16 에 추가된 io/fs 를 사용하는 방법 입니다.

func handleFunc(c *gin.Context) {
	img, _ := os.Open("q.PNG")
	imgData, _ := ioutil.ReadAll(img)

	c.Data(http.StatusOK, "image/png", imgData)
}

[]byte 형태 파일을 전달 할 때 사용합니다. 주로 파일 후 처리 후 (이미지 자르기 등) 파일을 전송할 때 사용 합니다.

func handleFunc(c *gin.Context) {
	img, _ := os.Open("q.PNG")
	imgFileInfo, _ := img.Stat()

	c.DataFromReader(
		http.StatusOK,		// http status code
		imgFileInfo.Size(),	// size
		"image/png",		// Content-Type
		img,				// io.Reader
		nil,				// extraHeaders - 캐시나 파일 정보를 작성
	)
}

io.Reader 형태 파일 데이터를 전송 할 때 사용합니다.

라우터 에서 제공

r := gin.New()

r.Static("/assets", "/var/www")
// 첫번째 인자로 접속시
// 두번째 인자로 입력된 디렉토리를 루트로 파일 제공

r.StaticFS("/assets", http.Dir("/var/www"))
// 위 함수와 동일
// io/fs 패키지를 두번째 인자로 받음

r.StaticFile("/cat", "./cat.PNG")
// 파일 하나를 제공 할 때 사용
// 두번째 인자로 파일의 경로를 받음

헤더

HTTP 헤더 설정에 따라 브라우저에 브라우징 되거나, 파일이 다운로드 될 수 있습니다. 명시적으로 파일이 다운로드 되도록 하려면 다음 헤더를 설정 합니다.

Content-Type: <파일 타입>

파일의 타입을 명시합니다. 다음 링크를 참고하세요

MIME 타입 - HTTP | MDN
MIME 타입이란 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘입니다: 웹에서 파일의 확장자는 별 의미가 없습니다. 그러므로, 각 문서와 함께 올바른 MIME 타입을 전송하도록, 서버가 정확히 설정하는 것이 중요합니다. 브라우저들은 리소스를 내려받았을 때 해야 할 기본 동작이 무엇인지를 결정하기 위해 대게 MIME 타입을 사용합니다.
MIME 타입의 전체 목록 - HTTP | MDN
다음은 일반적인 확장자로 정렬된, 문서 타입과 관련된 MIME 타입의 포괄적인 목록입니다.

Content-Length: <파일 바이트 사이즈>

파일 크기를 바이트 단위 10진수 숫자로 명시합니다.

Content-Disposition: attachment; filename="<파일 이름>"

파일이 다운로드 될 파일임을 명시 합니다. 파일이름이 한국어 일 경우 인코딩이 필요할 수 있습니다.

Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt

Content-Transfer-Encoding: binary

전송 인크딩을 바이너리로 설정 합니다.

Cache-Control: no-store, no-cache, must-revalidate, Post-Check=0, Pre-Check=0

파일 캐시를 비활성화 합니다.

클라이언트 -> 서버 파일 전달 (업로드)

단일 파일

func main() {
	r := gin.New()
    
    // 업로드 메모리 용량 설정 - 필요시 사용
	r.MaxMultipartMemory = 8 << 20  // 8 MiB

	r.GET("/", serveUploadPage)
	r.POST("/upload", uploadHandler)

	r.Run(":8080")
}

// 싱글 파일 업로드 폼 HTML
const uploadPage string = `<html>
<head>
	<title>파일 업로드</title>
	<meta charset="utf-8">
</head>
<body>
	<h2>파일 업로드</h2>
	<form action="/upload" method="POST" enctype="multipart/form-data">
		upload file: <input type="file" name="file">
		<input type="submit" value="Submit">
	</form>
</body>
</html>`

// 업로드 폼 제공 핸들러
func serveUploadPage(c *gin.Context) {
	c.Data(http.StatusOK, "text/html", []byte(uploadPage))
}

// 싱글 파일 업로드 핸들러
func uploadHandler(c *gin.Context) {
	// Single file
	file, _ := c.FormFile("file")
	log.Println(file.Filename + " uploaded")
    
    // 파일 저장

	// 방법 1.
	// 기본 제공 함수로 파일 저장
	c.SaveUploadedFile(file, filepath.Join("./uploaded", file.Filename))

    //

	c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
}

싱글 파일 업로드 핸들러 입니다. 기본 제공하는 SaveUploadedFile() 를 사용하거나, FormFile() 이 리턴 한 fileOpen() 함수로 Reader 인터페이스로 받아 사용할 수 있습니다.

주의 사항으로 업로드 된 파일의 이름 file.Filename 은 신뢰해선 안됩니다.

참고: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives

src, _ := file.Open()
defer src.Close()

out, _ := os.Create(filepath.Join("./uploaded", file.Filename))
defer out.Close()
io.Copy(out, src)

SaveUploadedFile() 함수 대신 위처럼 직접 업로드한 파일 Reader 에 직접 접근 할 수 있습니다.

다중 파일

func main() {
	r := gin.New()
    
    // 업로드 메모리 용량 설정 - 필요시 사용
	r.MaxMultipartMemory = 8 << 20  // 8 MiB

	r.GET("/", serveUploadPage)
	r.POST("/upload", uploadHandler)

	r.Run(":8080")
}

// 다중 파일 업로드 폼 HTML
const uploadPage string = `<html>
<head>
	<title>파일 업로드</title>
	<meta charset="utf-8">
</head>
<body>
	<h2>파일 업로드</h2>
	<form action="/upload" method="POST" enctype="multipart/form-data">
		upload file: <input type="file" name="upload[]" multiple>
		<input type="submit" value="Submit">
	</form>
</body>
</html>`

// 업로드 폼 제공 핸들러
func serveUploadPage(c *gin.Context) {
	c.Data(http.StatusOK, "text/html", []byte(uploadPage))
}

// 다중 파일 업로드 핸들러
func uploadHandler(c *gin.Context) {
	// Multipart form
	form, _ := c.MultipartForm()
    
    // Uploaded files
	files := form.File["upload[]"]

	// for range 로 업로드한 파일 순회
	for _, file := range files {
		log.Println(file.Filename)
		c.SaveUploadedFile(file, filepath.Join("./uploaded", file.Filename))
	}
	c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
}