본문 바로가기
GO Lang

GO_웹 제작(3)

by u0jin 2020. 6. 17.

목표 : 파일 과 같은 데이터를 전송하고 받아오기 & 테스트코드 실행

(* 파일 업로드 한후, 업로드 된 파일이 업로드가 잘 되었는지 확인하기 위한 테스트 코드)

 

이제 데이터를 주고 받는 과정을 만들어보고 제대로 돌아가는지 확인해 보고자 합니다.

파일 데이터를 업로드를 하기위해서 HTML을 만들고 간단한 동작을 실행해 보겠습니다.

 


사용된 코드 : main.go / main_test.go / index.html

 

< main.go >

package main

import (
	"fmt"
	"net/http"
	"os"
	"io"
)

func uploadsHandler(w http.ResponseWriter, r *http.Request) {
	uploadFile, header, err := r.FormFile("upload_file")
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}
	defer uploadFile.Close()

	dirname := "./uploads"
	os.MkdirAll(dirname, 0777)
	filepath := fmt.Sprintf("%s/%s", dirname, header.Filename)
	file, err := os.Create(filepath)
	defer file.Close()
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(w, err)
		return
	}
	io.Copy(file, uploadFile)
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, filepath)
}

func  main(){

	http.HandleFunc("/uploads", uploadsHandler)

	http.Handle("/",http.FileServer(http.Dir("public")))

	http.ListenAndServe(":3000",nil)
}

 

 

< index.html >

<html>
<head>
<title>Created WebServer in golang</title>
</head>
<body>
<p><h1>File Transfer</h1></p>
<form action="/uploads" method="POST" accept-charset="utf-8" enctype="multipart/form-data">
    <p><input type="file" id="upload_file" name="upload_file"/></p>
    <p><input type="submit" name="upload"/></p>
</form>
</body>
</html>

 

< main_test.go >

package main

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"github.com/stretchr/testify/assert"
)

func TestUploadTest(t *testing.T) {
	assert := assert.New(t)
	path := "C:/Users/yujin/Pictures/GO_WEB/pass.png"
	file, _ := os.Open(path) 

	defer file.Close()

	os.RemoveAll("./uploads")

	buf := &bytes.Buffer{}

	writer := multipart.NewWriter(buf) 
	multi, err := writer.CreateFormFile("upload_file", filepath.Base(path))
	assert.NoError(err)
	io.Copy(multi, file)
	writer.Close()



	res := httptest.NewRecorder()
	req := httptest.NewRequest("POST", "/uploads", buf)

	req.Header.Set("Content-type", writer.FormDataContentType())

	uploadsHandler(res, req)

	assert.Equal(http.StatusOK, res.Code)

	uploadFilePath := "./uploads/" + filepath.Base(path)
	_, err = os.Stat(uploadFilePath)
	assert.NoError(err)

	uploadFile, _ := os.Open(uploadFilePath)
	originFile, _ := os.Open(path)
	defer uploadFile.Close()
	defer originFile.Close()

	uploadData := []byte{}
	originData := []byte{}
	uploadFile.Read(uploadData)
	originFile.Read(originData)

	assert.Equal(originData, uploadData)
}

 


코드를 하나씩 살펴 보도록 하겠습니다.

 

  • main.go
package main

import (
	"fmt"
	"net/http"
	"os"
	"io"
)

func uploadsHandler(w http.ResponseWriter, r *http.Request) {
/*
	전송된 파일은 Request에 실려서 온다.
    실려오는 파일을 읽어야 한다.
*/

/*
	///////////////////////Form 에서 파일을 read 하는 부분 시작
*/


	uploadFile, header, err := r.FormFile("upload_file")
/*
	FormFiled은 id가 "upload_file" 인 파일을 받았을때,
    리턴값이 세개가 나온다.
    1번 : 파일 = uploadFile
    2번 : 헤더 = header
    3번 : 에러 = err

*/


	if err != nil {
/* 만약에 에러가 있다면 (ex 파일의 부재) */

		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
        /* 에러를 그냥 반환해줌 */
		return
	}

	defer uploadFile.Close()

/*
	///////////////////////Form 에서 파일을 read 하는 부분 끝
*/


/*
	///////////////////////새로운 파일을 저장할 공간을 만드는 부분 시작
*/


	dirname := "./uploads"
/*
	파일을 업로드 받은후, 파일을 저장해야 하는데 그 파일을 어디에 저장해야할지 정해야한다.
    "uploads" 라는 폴더 생성
*/

	os.MkdirAll(dirname, 0777)
/*
	MkdirAll함수를 써서 폴더를 만들고, ( 디렉토리 이름, 파일모드 ) 를 넣어주어야함
    여기서 파일모드는 0777 을 썼는데, 777은 읽고 쓰고 실행 모두 다 되는것을 뜻한다.
*/

	filepath := fmt.Sprintf("%s/%s", dirname, header.Filename)
/*
	filepath를 저장함 -> 폴더명, 파일명을 가져옴 
*/

	file, err := os.Create(filepath)
/*
	os.Create 함수를 이용해 파일을 만듬
    비어있는 파일이다. 이부분은 그냥 파일 핸들만 만들어낸것임
*/

	defer file.Close()
/*
	만들어준 파일은 꼭 닫아줘야한다.
    파일은 핸들을 사용하는데 이 핸들이 os자원이기 때문에 꼭 반납을 해줘야하고
    반납을 안하면 문제가 생길수 있기때문에 함수가 종료되기 전에 꼭 닫아줘야한다.
*/
    

	if err != nil {
/* 만약에 에러가 있다면 (ex 디렉토리가 다 차는경우) */

		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(w, err)
        /* 에러를 그냥 반환해줌 */
		return
	}
    

/*
	///////////////////////새로운 파일을 저장할 공간을 만드는 부분 끝
*/


/*
	///////////////////////복사하는 부분
*/
	
	io.Copy(file, uploadFile)
/*
	에러가 없다면 , io.Copy 함수를 써서 복사한다.
    uploadFile 을 file에 복사함
*/


/*
	///////////////////////응답을 보내는 부분 
*/
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, filepath)
/* 어디에 업로드 됬는지 파일경로 보여준다 */

}

func  main(){

	http.HandleFunc("/uploads", uploadsHandler)
/* 업로드 경로 에 대한 핸들러를 만들어줌 */ 


	http.Handle("/",http.FileServer(http.Dir("public")))
/* 인덱스 경로 - 파일서버 (퍼블릭 경로) */
 
	http.LisetAndServe(":3000",nil)

/* 가장 고전적인 파일 웹서버 - public 폴더 안에 있는 파일들을 엑세스 할수 있도록 열어주는것 */
}

 

file upload : 파일선택 후 / 파일제출 후

 

디렉토리 안 :  upload 된 파일

 

Go 언어의 defer 키워드는 지연실행을 말한다.

특정 문장 혹은 특정 함수를 나중에 실행시키게 한다.

(defer를 호출하는 함수가 리턴하기 직전에 실행한다.)

일반적으로 Clean-up 작업을 위해 사용된다.

이후 문장에서 어떤 에러가 발생하더라도 항상 파일을 닫을 수 있도록 한다.

 

비슷하게 Go의 내장함수 중에 panic()함수는 현재 함수를 즉시 멈추고 현재 함수에 defer함수들을 모두 실행한후 즉시 리턴한다.

panic 모드 실행 방식은 상위함수에서도 똑같이 적용되고 , 마지막에는 프로그램이 에러를 내고 종료하게 된다.

 

파일모드 에서 777은 리눅스 권한 변경과 같다.

 

  • index.html
<html>
<head>
<title>Created WebServer in golang</title>
</head>
<body>
<p><h1>File Transfer</h1></p>
<form action="/uploads" method="POST" accept-charset="utf-8" enctype="multipart/form-data">
 <!-- 입력폼 만듬 -->
    <p><input type="file" id="upload_file" name="upload_file"/></p>
    <p><input type="submit" name="upload"/></p>
     <!-- 
     	업로드 핸들러를 만들지 않을경우 ,  에러가 나오게 된다.
        -->
</form>
</body>
</html>

 

index.html 결과창

 

  • main_test.go
package main

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"github.com/stretchr/testify/assert"
)

func TestUploadTest(t *testing.T) {
	assert := assert.New(t)
	path := "C:/Users/yujin/Pictures/GO_WEB/pass.png"
/*
	경로 설정에서 " 가 아닌 ` 를 사용한다면, 역슬래시를 넣을수 있지만, 
    특수 문자기 때문에 사 그냥 슬래시를 넣어야한다.
*/

	file, _ := os.Open(path) 
/*
	os.Open함수를 이용해서 파일을 연다.
    파일이 없으면 당연히 에러가 나고, 로컬에서 확인하는것이기 때문에
    따로 에러처리는 하지않는걸로 함
*/

	defer file.Close()
/* 오픈된 파일은 꼭 닫아줘야함 */
	
    
	os.RemoveAll("./uploads")
/* 
	os.RemoveAll()함수를 사용하여 해당 디렉토리에 있는 파일을 다 지움 
    제대로 업로드가 되는지 확인하기 위한 작업이다.
*/


	buf := &bytes.Buffer{}
/*
	바이트패키지의 버퍼를 만들어준다.
    인스턴스 주소를 적어두고 해당 버퍼를 넘겨줄거임
*/

	writer := multipart.NewWriter(buf) 
/*
	웹으로 파일을 넘겨줄때, MIME 이라는 포맷을 사용하는데, 해당 포맷을 사용하기 위해
    mutipart write 를 만들어 줘야한다.
    
    NewWriter는 버퍼를 만들어줘야하고, 데이터가 버퍼에 저장된다.
   	writer 라는 인스턴스 생성
*/
    


	multi, err := writer.CreateFormFile("upload_file", filepath.Base(path))
/*
	writer는 파일을 하나 만드는데,
    "upload_file" 이라는 빌드이름과  filepath.Base(path) 라는 파일이름이 들어가게 된다.
    
    파일이름은 경로에서 파일이름만 잘라내었고, 
    리턴값은 io.Writer ,err 이다.
*/
    

	assert.NoError(err)
/* 에러가 없으면 */

	io.Copy(multi, file)
/*  file 에서 가져온 데이터를 multi에 복사한다. */    
    
	writer.Close()



/*	
	//////////////테스트웹서버를 불러서 확인해는 부분 
*/

	res := httptest.NewRecorder()
	req := httptest.NewRequest("POST", "/uploads", buf)
/*
	post라고 두고, 타겟은 uploads , body는 데이터 이다.
    데이터는 buf에 실려있기 때문에 버퍼를 보냄
*/

	req.Header.Set("Content-type", writer.FormDataContentType())
/*
	request 에서 이게 어떤 데이터인지 알려줘야한다.
    서버가 알려줘야만 읽을수 있고 알려주지않으면 에러가 난다.
   	
    이게 폼컨텐츠라는것을 알려준다.
*/
    
    

	uploadsHandler(res, req)
/* 핸들러부름 */

	assert.Equal(http.StatusOK, res.Code)
/* 같은지 검사함 */

/* 
	/////////////////// 업로드 되는것을 다 확인하는 부분 끝
*/


/*
	/////////////////// 실제로 파일이 같은지 확인하는 부분 시작
*/

	uploadFilePath := "./uploads/" + filepath.Base(path)
/*
	업로드 되는경로는 uploads 라는 폴더에 + filepath.Base(path) 라는 파일이름을 합침
*/


	_, err = os.Stat(uploadFilePath)
/*
	파일이 잘 들어있는지 확인해야하고, stat()함수를 쓰면 os.FileInfo 를 가져다 준다.
    info가 없으면 못가져오고 에러가 나올것임
*/
    
    
	assert.NoError(err)
/* 에러가 없는지 확인 */

	uploadFile, _ := os.Open(uploadFilePath)
	originFile, _ := os.Open(path)
/*
	에러가 없는지확인하는 라인을 통과한다면, 파일이 있다는 소리와 같다.
    해당 파일을 읽어서 업로드된 파일과 기존파일이 같은지 확인하도록 한다.
*/


	defer uploadFile.Close()
	defer originFile.Close()
/* 열린 두 파일 모두 닫아줘야한다. */



	uploadData := []byte{}
	originData := []byte{}

	uploadFile.Read(uploadData)
	originFile.Read(originData)
/*
	파일내용물을 읽은 다음, 같은지 확인할수 있기때문에,
    Read()함수를 이용해서 파일을 읽는다.
    Read()함수 안에는 바이트배열이 들어가게된다.
    
    uploadFile 은 uploadData에서 읽고,
    originFile 은 originData에서 읽는다.
*/

	assert.Equal(originData, uploadData)
/* 두 데이터가 같은지 확인 */

}

 

'GO Lang' 카테고리의 다른 글

GO_웹 제작(5)  (0) 2020.08.13
GO_웹 제작(4)  (0) 2020.06.23
GO_웹 제작(Test 환경 구축)  (0) 2020.06.13
GO_웹 제작(2)  (0) 2020.06.08
GO_웹 제작(1)  (2) 2020.06.07

댓글