웹개발일지

[코멘토 실무PT 후기 챌린지_ GoLang_실무PT 강의 2주차 후기] 본문

카테고리 없음

[코멘토 실무PT 후기 챌린지_ GoLang_실무PT 강의 2주차 후기]

hee_log 2022. 12. 26. 00:00
728x90

 

2주차 [22.12.19 ~ 22.12.23]

 코멘토 GoLang 실무PT에 참여하며 2주차 수업을 듣고 복습 중에 있다. 2주차 진도로는 GoLang 프레임워크인 gin을 사용하여 Image 를 업로드 하는 Restful API를 구현하였다. 과제로는 html파일과 연동할 이미지 api를 구현하기로 하였다. 

 

코멘토 클래스룸

 

 gin이 프레임워크인가 라이브러리인가 명확하지가 않아 찾아보니 둘의 차이가 흐름에 대한 제어 권한이라는데, 라이브러리는 그 제어권한이 개발자한테 있다고 하는데 무슨말인지 모르겠다.. 다만 프레임워크는 꼭 가져다 써야하는 코드가 있고 일정한 틀이 정해져있고 그 안에 들어가서 쓴다는 개념이 강한 반면 라이브러리는 가져다 쓰는 개념이 강한데 아직 뭐가 뭐다라고 구별하기엔 이해가 부족한 것 같다. 

 

 각설하고 그럼 배운 내용을 정리하려고 한다. 구체적인 것을 배우기 전에 웹서버의 기초적인 개념을 배웠다. 

 

1. 웹서버 동작 원리

 

 클라이언트와 서버가 데이터를 주고 받으려면 주고받을 데이터에 대한 규약이 필요한데 이를 웹에서는 HTTP 프로토콜이라고 한다. 

클라이언트(웹 브라우저)가 데이터를 요청하면 서버가 요청받은 데이터를 보내주는 것이 1차적인 이해 개념이다. 

 

 웹이 서버에 리퀘스트를 할 때 데이터 요청 정보들을 담은 자료구조를 헤더라고 한다. 리퀘스트 헤더가 있고 그 안에 메서드, 상태코드 등이 통틀어 정의되어있다. 그 코드들을 HTTP라고 할 수 있을 것 같다. HTTP는 클라이언트-서버 혹은 요청-응답 프로토콜이라고도 한다. 

 

 미리 코드와 함께 개념을 덧붙여 보자면  앞으로 header, status code, res, req 그리고 CRUD 를 구현한 HTTP 메서드들을 앞으로 많이 사용하게 될 것인데, 특히나 HTTP 메서드들을 잘 지켜서 구현하면 Restful 하다고 표현을 하는 것 같다. rest라는 개념을 소프트 아키텍처의 한 형식인데 restful하다는 것은 rest 아키텍처를 잘 따르는 시스템을 뜻한다고 한다. 아, HTTP 메서드는 클라이언트와 서버간 오가는 데이터들에 특정 동작들을 부여해주는 개념이다. 주로 구현하는 개념이 데이터 조회, 등록, 삭제, 변경이다. 

 

 

2. gin 프레임워크

https://github.com/gin-gonic/gin 

 

GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better perf

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin. - ...

github.com

 gin 의 깃헙 주소이다. Star가 65.2k다. Go의 우수한 웹프레임워크로 잘 알려져있다. 첫 설명으로 마티니에 비해 월등한 성능을 자랑한다고 되어있는데 어떤 차이가 있는 것인지 궁금하다. 우선 설치방법 부터 기초 소스 등을 살펴보며 활용해보았다. 

 

라이브러리를 설치하려면 우선 내 작업 폴더에 go.mod 파일이 설치가 되어있어야한다. 

go mod init

 

 

vscode 터미널창에서 명령어를 입력하면 위치하는 폴더 아래 go.mod 파일이 생긴다. 

생성된 go.mod 파일

go의 현재 버전이 나와있고 내 작업폴더가 모듈로 생성되어있다. 그리고 나서 다음 명령어로 gin을 설치한 뒤 작업파일에 import까지 해준다. 

go get -u github.com/gin-gonic/gin

 다른 라이브러리들을 사용하면서 초반에 go mod파일을 항상 설치하지 않고 go get을 진행하다가 아래와 같은 오류를 접했다. 

더보기

go: go.mod file not found in current directory or any parent directory.
        'go get' is no longer supported outside a module.
        To build and install a command, use 'go install' with a version,
        like 'go install example.com/cmd@latest'
        For more information, see https://golang.org/doc/go-get-install-deprecation
        or run 'go help get' or 'go help install'.

 내용은 대략 'go module 밖에서 go get은 더이상 지원되지 않는다. go module을 써야 go get 사용이 가능하다. go module 밖에서 사용하려면 go install을 사용해라.'라는 건데  디렉토리에 go.mod 파일이 없어서 나는 오류이다. 버전관련 내용이 있는 것을 보니 버전이 업그레이드 되면서 go mod의 여부에 따라 go get이냐 go install이냐로 구분이 된 것 같다.

 이에 대한 이해는 모듈과 패키지의 동작 경로와 관련이 있는 것 같은데, go module을 쓰게 되면 go 폴더 아래 pkg 폴더아래 mod 아래에서 찾게된다고 한다. 참고 글에 의하면 모든 패키지들은 로컬 유저 디렉토리 go/src 아래 소스 파일들이 저장되고 , Go는 바로 컴파일해서 go/pkg 아래 .a파일을 생성한다고 한다. '.a'파일이 무엇인가 하고 보니 라이브러리 정적 파일이라고 하는데 이건 예전 버전이라고 한다. 

 

 이렇게 go mod 파일 설치 > gin 라이브러리 설치를 마친 후 작업할 파일에서 gin의 깃헙 주소를 import 해서 사용할 수 있다. 

import "github.com/gin-gonic/gin"

 

 

 go mod 파일은 의존성을 관리하는 파일이다. 의존성관리는 내가 사용하는 라이브러리들을 관리하는 건데 자주 쓰지 않는 라이브러리들을 정리해주는 역할을 한다. 

go mod tidy

 위 명령어를 사용하여 사용하고 있는 라이브러리를 mod 파일에 추가할 수 있다. 이걸 하면 import한 라이브러리 경로의 빨간줄이 사라진다. 이 명령어는 항상 작동하는 것은 아니고 local의 경로에 gin이 저장되어있었기 때문에 가능하다고 한다. 

 


3.  gin-gonic quick start 

  gin 프레임워크 깃헙 주소의 예제 코드에 실습 내용이 들어간 코드이다. 

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
	"fmt"
)

type Person struct {
	Name    string `form:"name"`
	Address string `form:"address"`
}

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {  // Get메서드에 url 매핑, 
    //익명함수 내 조건 (경로와 GET메서드)을 충족하는 클라이언트 요청을 처리하는 작업 정의 
		c.JSON(http.StatusOK, gin.H{  
			"message": "pong",  
		})
	})

	r.StaticFile("/image", "test.jpeg")  
     
	r.POST("/upload", func(c *gin.Context) {
		// Single file
		file, _ := c.FormFile("file")  
		
		fmt.Println(file.Filename)
	
		// Upload the file to specific dst.
		c.SaveUploadedFile(file, file.Filename)  
	
		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	  })

	r.GET("/test", func(c *gin.Context) { 
		person := Person{}
		err := c.ShouldBind(&person)
		if err != nil {
			fmt.Println("Bind Error")
		}

		c.JSON(200, person)
	})

	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")

}

gin.Default() 

 gin.Default()를 사용하여 서버를 구성하는데 필요한 라우터를 생성했다. gin을 사용하려면 gin 엔진 객체가 필요하다. gin의 Default()함수는 Engine의 인스턴스를 반환한다.이 객체는 url을 정의하고 http 메서드를 지정할 때 사용한다. gin의 내부 코드를 살펴보면 Engine 은 구조체로 다음과 같이 구성되어 있다. 

gin 프레임워크 Engine 구조체 내부 코드

 Engine 구조체가 가진 RouterGroup 필드는 GET()메서드를 구현하고 있어 생성된 인스턴스 r로 GET()메서드를 호출할 수 있게 되는 것이다.  

 

 

*gin.Context

이 객체는 요청 확인과 처리, 응답에 필요한 기능을 제공한다. 

 

 c.JSON() 

 gin의 context 객체에 있는 HTTP 응답을 해주는 코드이다. 

c.JSON(http.StatusOK, gin.H{ 
            “message”: “pong”, 
        })

r.StaticFile() 

// StaticFile registers a single route in order to serve a single file of the local filesystem.
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
	return group.staticFileHandler(relativePath, func(c *Context) {
		c.File(filepath)
	})
}

첫번째 인자로 요청 경로를 두 번째 인자로 보내줄 파일을 받는다. 이 또한 RouterGroup에 속한 메서드인 것을 확인 할 수 있다. 리턴 함수를 보니 File()을 통해 body stream 에 파일을 써주는 듯 하다. static file을 serve하는 방법은 이 외에도 아래와 같이 할 수 있다. 

	router.Static("/assets", "./assets")
	router.StaticFS("/more_static", http.Dir("my_file_system"))
	router.StaticFile("/favicon.ico", "./resources/favicon.ico")

 

c.FormFile() 

 클라이언트로부터 "file"을 키로 받아 value로 해당하는 파일을 불러와 생성해준다. 

 

c. ShouldBind()

 바인딩은 프레임워크를 사용해서 웹서버 개발을 할 때 활용할 수 있는 중요한 기능중 하나인 것 같다. 그러니까 net/http 를 활용하면 쉽게 웹서버를 구현할 수 있는데 이런기능이 없어 구현할 코드가 늘어난다. 바인드의 목적은 클라이언트로 부터 서버에 저장할 수 없는 형식의 데이터를 받았을때 유효성 검사를 하기 위함이다. 우리는 gin에 요청 데이터형식이 서버의 데이터와 잘 매칭되는지를 바인딩 태그를 작성하여 알릴 수 있다. 구조체 필드에 백틱으로 지정한다. 

  gin 에는 바인딩하는 방법에 두 가지가 있다. Mustbind, ShouldBind. 우리는 ShouldBind를 사용하고있다. 둘의 차이점을 살펴볼 수 있다. 

더보기

 

Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".

Also, Gin provides two sets of methods for binding:

  • Type - Must bind
    • Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader, BindTOML
    • Behavior - These methods use MustBindWith under the hood. If there is a binding error, the request is aborted with c.AbortWithError(400, err).SetType(ErrorTypeBind). This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. Note that if you try to set the response code after this, it will result in a warning [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. If you wish to have greater control over the behavior, consider using the ShouldBind equivalent method.
  • Type - Should bind
    • Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader, ShouldBindTOML,
    • Behavior - These methods use ShouldBindWith under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.

 

SaveUploadedFile() 

 특정 path 에 파일을 써주는 함수이다. 막상 코드를 직접 짜려고 하니 FormFile()함수랑 헷갈리는 것 같다.. 

 


과제 

  과제로 완성된 html 파일에 rest api를 구현할 서버를 만들어보는 것을 했다. html 에 연결하고 GET, POST, UPDATE, DELETE 를 구현했다. 하면서 이해가 잘 안되는 부분이 많아 많이 찾아보면서 각 메서드들이 언제 쓰여야 하는지 좀 더 명확해진 것 같은데 코드 구현은 잘 안된다. 수업때 했던 코드를 복사해놓고 수정중에 있다. 

 

디렉토리 구조 

 작업중인 works 폴더의 구조다. 

 

works 

.
├── README.md
├── file
├── go.mod
├── go.sum
├── index
│   └── index.html
├── internal
│   └── global
│       └── global.go
├── main.go
└── pkg
    ├── api
    │   └── image.go
    └── router
        └── router.go

 

main.go 

package main

import (
	"comento/works/pkg/router"
	
)



func main(){
	r := router.Router()
	r.Run(":8081")

}

 

router.go 

package router

import (
	"comento/works/pkg/api"
	"github.com/gin-gonic/gin"
	"net/http"
)

func Router() *gin.Engine {
	r := gin.Default()
	r.LoadHTMLFiles("index/index.html")  // have to tell Gin to load the HTML files first using
	r.GET("/", func(c *gin.Context) {
		c.Header("Content-Type", "text/html")
		c.HTML(http.StatusOK, "index.html", gin.H{})
	})
	r.POST("/post", api.CreateImage)   
	r.GET("/images", api.ServeFile)  
	r.PUT("/put", api.Update)
	r.DELETE("/delete", api.DeleteImage)

	return r
}

라우터 작업에서 기본 경로로 GET요청이 들어왔을 때 html 파일을 띄워주는 코드를 짜면서 많이 헤맸다. 여기서 각 메서드에 연결시킬 핸들러를 별도 파일에 작성했다. 

 

image.go 

 아직 완성시키지 못한 코드이다.. 

package api

import (
	"comento/works/internal/global"
	"fmt"
	// "mime/multipart"
	"net/http"
	"strconv"
	"github.com/gin-gonic/gin"
)



// get요청이 들어왔을 때 실행될 함수 
func ServeFile(c *gin.Context) {
	file, _ := c.FormFile("file")
	c.JSON(http.StatusCreated, file.Filename)
}

type Response struct {
	Res string `json:"res"`
}

type RequestFile struct{
	File string `json:"file"`
}

// POST 요청시 실행될 함수 
func CreateImage(c *gin.Context) { 
	req := RequestFile{}  
	file,_ := c.FormFile("file")
	if err := c.ShouldBind(&req); err != nil { 
		fmt.Println(err)
		c.JSON(http.StatusBadRequest, Response{Res:"file is not valid"})  
		return   
	}
	if err := c.SaveUploadedFile(file, "file/"+strconv.FormatInt(global.MaxImageNumber, 10)); err != nil{  
		fmt.Println(err)
		c.JSON(http.StatusBadRequest, Response{Res: "file can't be served"})
		
		return
	}
	global.MaxImageNumber++
	c.JSON(http.StatusOK, Response{Res:"successed to served the file." + strconv.FormatInt(global.MaxImageNumber,10)})
}



type RequestURI struct { 
	ImageId string `uri:"id"`
}



func Update(c *gin.Context) {
	file, _ := c.FormFile("file")
	fmt.Println(file.Filename + "file is uploaded to folder")
	
	c.Data(http.StatusOK, "image/", []byte(file.Filename))
} 

func DeleteImage(c *gin.Context) {

}

 global 변수를 작성하는 파일을 internal/global 패키지로 따로 만들지 않아 GOPATH에 등록이 안되는 오류가 났었다. 

 

 

 

 2주차 수업을 듣고 과제를 진행하면서 restapi 를 구현하는 과정에 대한 이해도를 높일 수 있었다. 아직 부족한 점이 많지만 과제를 완수하고 나면 실력이 많이 향상될 것이라고 느낀다. 3주차 수업때 내가 짠 코드와 과제 전반에 대한 피드백 시간을 갖는다. 전까지 최대한 공부해서 이해도를 높이고 더 깊게 공부할 수 있도록 해야겠다.

 

 코멘토 실무 PT를 통해 혼자서 공부했더라면 도중에 이해를 하는 것에 포기하는 과정이 많았을 것 같은데 멘토 강사분과 1:1 질문 학습 과정, 코멘토 클래스룸내 무한 반복 강의 시청으로 1주일간 많은 걸 얻어갈 수 있었던 것 같다. 이번주는 Go 기초문법에 대한 질문도 많이했고 restapi 구현을 하는것에 이해도를 높일 수 있었다. 앞으로 실력을 빌드업시킬 일만 남았다!! 

 

 

코멘토 1:1 질문과정

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---

참고 글 

https://joshua1988.github.io/web-development/http-part1/

https://litaro.tistory.com/entry/Go-%EC%96%B8%EC%96%B4-%EC%B4%88%EB%B3%B4%EC%9D%98-Go-modules-%EC%A0%95%EB%A6%AC-%EB%85%B8%ED%8A%B8-1