Language/Golang

Golang (Go언어) Go로 만드는 웹 (4)

HeeWorld 2025. 1. 8. 15:23

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스코트 Gopher(고퍼)

 

✓ 테스트 코드 실행하기

- 기존에 테스트하던 파일에 새로운 TestFoo라는 코드를 새로 만듦.

func TestFooHandler_WithoutJson(t *testing.T) {
	assert := assert.New(t)

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/foo", nil)

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

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

 

- 위 코드 내용을 저장하고 터미널에 'go test'를 입력하면, FAIL이 되는 것을 확인할 수 있음.

  → 이유는 main.go 파이르이 fooHandler 코드는 Json Body가 없는 경우 Decode가 실패됨.

       Json Decode 실패할 경우 err가 나오면 "Bad Request:"를 반환하게 됨. (Status는 BadRequest~가 됨)

값이 200이 와야하는데 400이 와서 FAIL 된 것을 확인

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

 

- Status를 BadRequest로 변경하고 저장하고 'go test'를 실행하면 OK가 되는 것을 확인할 수 있음.

 

✓ JSON을 추가해서 코드 실행하기

func TestFooHandler_WithoutJson(t *testing.T) {
	assert := assert.New(t)

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/foo",
		strings.NewReader((`{"first_name":"Hello", "last_name":"World", "email":"helloworld@gmail.com" }`)))

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusCreated, res.Code)
}

 

- Request를 "POST"로 변경하고, Body에 strings.NewReader를 통해 string을 Reader로 바꿔줌.

   strings.NewReader를 통해 글자들이 io.Reader로 바뀌어서 Request로 보내줄 수 있음.

   그리고 응답을 StatusCreated로 바꿈.

   app.go 파일에 fooHandler 코드 확인 시, Decode가 잘 된 경우 응답을 StatusCreated로 보내줌을 확인할 수 있음.

   저장하고 'go test'를 실행하면 PASS가 되는 것을 확인할 수 있음.

 

  > 안에서 ""을 사용해야 할 경우 ``을 사용!

 

✓ 응답 확인하기

- 위 코드에 아래 내용 추가

	user := new(User)
	err := json.NewDecoder(res.Body).Decode(user)
	assert.Nil(err)
	assert.Equal("Hello", user.FirstName)
	assert.Equal("World", user.LastName)

 

- user Struct를 만들어서 응답된 결과를 user Struct로 다시 Decode를 진행함.

   json.NewDecoder를 사용해서 input값을 res.Body를 넣고, 이를 Decode함.

  실패할 경우 에러가 나오는데, 에러가 나올 경우 에러는 Nil이어야 하고

  FristName과 LastName은 각각 "Hello"와 "World"여야 한다는 내용

  저장하고 'go test'를 실행하면 모든 테스트가 다 PASS 된 것을 확인할 수 있음.

PASS된 것을 확인할 수 있음

 

 

파일 서버

- 파일을 전송/받을 수 있는 서버를 만들기 위해 새로운 디렉터리를 생성해주고, mian.go 파일을 새로 생성함.

package main

import "net/http"

func main() {
	http.Handle("/", http.FileServer(http.Dir("public")))

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

 

- 위 코드가 가장 고전적으로 파일 웹서버를 만드는 코드로, public 폴더 안에 있는 파일들에 access할 수 있는웹서버를 열어줌.

 

✓ index.html 작성

* 파일 서버 디렉터리 안에 public 디렉터리를 생성하고 그 안에 index.htm 파일을 생성

<html>
<head>
    <title>File Teansfer</title>
</head> 
<body>
    <p><h1>Let's save the file!</h1></p>
</body> 
</html>

 

- 위와 같이 html 형식으로 파일 내용을 작성한 뒤 저장하고 Dubugging 실행 후 웹으로 호출하고 결과 확인

정상적으로 결과를 확인할 수 있음

 

<html>
<head>
    <title>File Teansfer</title>
</head> 
<body>
    <p><h1>Let's save the file!</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>

 

- form과 input을 사용하여 html 코드를 위와 같이 추가하고 저장하여 실행 후 웹에서 결과 확인

결과 확인

- 파일 선택을 눌러 아무 파일을 선택하여 제출을 누르게 되면, upload Handler를 별도로 만들지 않아서 제출이 되지 않음.

 

package main

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

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)
}

 

- 웹에서 파일을 업로드하면 아래와 같이 업로드 된 경로가 출력됨.

   그리고 폴더 확인해보면 이미지 파일이 생긴걸 볼 수 있음.

파일 업로드 경로
업로드 된 파일

 

✓ 테스트 코드 만들기

- goconvey를 실행하시는데, 나는 안되니 'go test' 커멘드를 사용해서 확인 예정

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 := "/Users/Hee/file/uploads/Golang.jpeg"
	file, _ := os.Open(path)
	defer file.Close()

	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)

	uploadsHandler(res, req)
	assert.Equal(http.StatusOK, res.Code)
}

 

- 웹으로 파일을 전송할 때 MIME 포맷을 사용하는데, 이를 하기 위해 multipart.NewWriter()를 사용함.

   해당 writer에 CreateFile()을 사용하여 File을 생성함.

   fieldname을 upload_file, 파일 경로에서 filename만 사용하기 위해 filepath.Base를 사용함.

   테스트 코드를 저장하고 'go test'를 하면, FAIL이 되는 것을 확인할 수 있음.

   200번을 원했는데, 400번이 와서 FAIL이 됨.

FAIL을 확인할 수 있음

   데이터가 어떤 데이터인지 읽는 코드가 없어서 아래 코드를 req 다음 라인에 추가함.

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

 

- 추가하고, 다시 저장해서 'go test'시, PASS 되는 것을 확인할 수 있음.

 

✓ 파일 삭제 

- 해당 코드를 추가해서 파일을 삭제하고 다시 생성되게 테스트

os.RemoveAll("./uploads")

> uploads 디렉터리 안에 있는 파일을 지우고, 터미널에서 'go test'를 실행하면 다시 파일이 업로드 되는 것을 확인할 수 있음.

 

✓ 실제 파일이 같은지 확인

	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)

 

- 위 코드는 기존의 파일과 업로드 데이터가 같은 것인지 확인하는 코드로 assert 밑에 추가하고,

   저장하여 'go test'를 실행하면 PASS 값을 확인할 수 있음.

 

 

p.s 이거 33분짜리 영상인데 실제 나는 하다가 중간에 어디 꼬여서 build 에러나고 계속... 에러... 뱉어서

진짜 죽는 줄 알았다 ㅠㅠㅠㅠㅠㅠㅠ gpt한테 에러 물어보니 go 삭제하고 다시 깔라해서

그렇게 안하고, 에러 경로 뱉어준 파일 다 주석처리하고 돌리니 돌아갔다...

역시 주석 초 ㅣ고.....