일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- argo rollout
- Kubernetes 인증
- opentelemetry
- gitops
- opensearch
- mlops
- Litmus
- Pulumi
- 오퍼레이터
- Kopf
- 카오스 엔지니어링
- Argo
- argocd
- serving
- CANARY
- Kubeflow
- Continuous Deployment
- tekton
- keda
- Model Serving
- MLflow
- blue/green
- seldon core
- operator
- kubernetes operator
- knative
- CI/CD
- nginx ingress
- gitea
- Kubernetes
- Today
- Total
Kubernetes 이야기
Golang - Gin Framework 본문
Golang은 Google에서 만든 프로그래밍 언어로 C와 유사한 구문을 사용하여 정적으로 유형이 지정되고 컴파일를 지원하는 언어이다. Gin은 사용하기 쉽고 빠르게 설계된 Go 웹 프레임워크이다.
Gin에는 Go언로를 사용하여 개발을 좀 더 빠르고 쉽게 할 수 있도록 많은 기능을 제공한다.
- 내장 웹 서버
- 자동 라우팅
- 요청 및 응답을 처리하기 위한 도구
- 기능 추가를 위한 미들웨어
- 내장 Logger
- Template 지원
Gin은 다른 Web Framework보다 많은 Star를 가지고 있는 Framework이다.
Go 설치
# wget https://go.dev/dl/go1.19.linux-amd64.tar.gz
# tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
설치 후 .bashrc에 path 에 등록한다.
export PATH=$PATH:/usr/local/go/bin
Go 프로젝트 생성
프로젝트 폴더 생성 후 아래와 같이 go 명령어를 이용하여 Gin을 설치한다.
# mkdir myproject
# cd myproject
# go mod init github.com/kmaster8/gin/myproject
# go install github.com/gin-gonic/gin
sample code
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
sample code를 실행해 보자.
# go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
실행 시 "go: go.mod file not found in current directory or any parent directory." 오류가 발생하면 go env -w GO111MODULE=auto 를 실행한다.
브라우저에서 호출해 보자.
Parameter
func main() {
router := gin.Default()
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})
router.POST("/user/:name/*action", func(c *gin.Context) {
b := c.FullPath() == "/user/:name/*action" // true
c.String(http.StatusOK, "%t", b)
})
router.GET("/user/groups", func(c *gin.Context) {
c.String(http.StatusOK, "The available groups are [...]")
})
router.Run(":8080")
}
실행결과
# curl localhost:8080/user
404 page not found
# curl localhost:8080/user/kmaster8
Hello kmaster8
# curl localhost:8080/user/kmaster8/
kmaster8 is /
# curl localhost:8080/user/kmaster8/test
kmaster8 is /test
# curl -X POST localhost:8080/user/kmaster8/test
true
# curl localhost:8080/user/groups
The available groups are [...]
Querystring parameters
func main() {
router := gin.Default()
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
실행결과
# curl "localhost:8080/welcome?firstname=hong&lastname=gildong"
Hello hong gildong
Post Parameters
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
})
router.Run(":8080")
}
실행결과
# curl -X POST "localhost:8080/post?id=1234&page=1" -d "name=manu&message=this_is_great"
id: 1234; page: 1; name: manu; message: this_is_great
Upload File
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Single file
file, err := c.FormFile("file")
log.Println(file.Filename)
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
return
}
// Upload the file to specific dst.
dst := "/tmp/upload/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
return
}
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
실행결과
# curl -X POST http://localhost:8080/upload -F "file=@/tmp/a.txt" -H "Content-Type: multipart/form-data"
'a.txt' uploaded!
Grouping routes
func main() {
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
func loginEndpoint(c *gin.Context) {
getPath := c.Request.URL.String()
c.JSON(200, gin.H{
"pathInfo": getPath,
})
}
func submitEndpoint(c *gin.Context) {
getPath := c.Request.URL.String()
c.JSON(200, gin.H{
"pathInfo": getPath,
})
}
func readEndpoint(c *gin.Context) {
getPath := c.Request.URL.String()
c.JSON(200, gin.H{
"pathInfo": getPath,
})
}
실행결과
# curl -X POST http://localhost:8080/v1/login
{"pathInfo":"/v1/login"}
# curl -X POST http://localhost:8080/v2/login
{"pathInfo":"/v2/login"}
Custom Recovery behavior
func main() {
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
if err, ok := recovered.(string); ok {
c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
}
c.AbortWithStatus(http.StatusInternalServerError)
}))
r.GET("/panic", func(c *gin.Context) {
// panic with a string -- the custom middleware could save this to a database or report it to the user
panic("foo")
})
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "ohai")
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
실행결과
]# curl http://localhost:8080/
ohai
# curl http://localhost:8080/panic
error: foo
--> 서버 로그
2022/08/08 13:50:12 [Recovery] 2022/08/08 - 13:50:12 panic recovered:
GET /panic HTTP/1.1
Host: localhost:8080
Accept: */*
User-Agent: curl/7.29.0
foo
/root/go/myproject/recovery.go:28 (0x744166)
main.func2: panic("foo")
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x73e421)
(*Context).Next: c.handlers[c.index](c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/recovery.go:101 (0x73e40c)
CustomRecoveryWithWriter.func1: c.Next()
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x73d526)
(*Context).Next: c.handlers[c.index](c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/logger.go:240 (0x73d509)
LoggerWithConfig.func1: c.Next()
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x73c5f0)
(*Context).Next: c.handlers[c.index](c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:616 (0x73c258)
(*Engine).handleHTTPRequest: c.Next()
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:572 (0x73bf1c)
(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:2947 (0x6214eb)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:1991 (0x61dbc6)
(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1594 (0x4672a0)
goexit: BYTE $0x90 // NOP
[GIN] 2022/08/08 - 13:50:12 | 500 | 1.257669ms | ::1 | GET "/panic"
--> 재호출
# curl http://localhost:8080/
ohai
Model binding and validation
http form이든 json, xml 이든 동일한 json 모델에 맵핑이 가능하다.
// Binding from JSON
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
실행결과
# curl -v -X POST \
> http://localhost:8080/loginJSON \
> -H 'content-type: application/json' \
> -d '{ "user": "manu" }'
* About to connect() to localhost port 8080 (#0)
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /loginJSON HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Mon, 08 Aug 2022 05:40:21 GMT
< Content-Length: 100
<
* Connection #0 to host localhost left intact
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}[root@p-thlee-master myproject]#
Testing
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
위의 테스트 코드는 아래와 같다.
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
실행결과
=== RUN TestPingRoute
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> command-line-arguments.setupRouter.func1 (3 handlers)
[GIN] 2022/08/08 - 17:40:37 | 200 | 8.403µs | | GET "/ping"
--- PASS: TestPingRoute (0.00s)
PASS
ok command-line-arguments 0.010s
Swagger
# go install github.com/swaggo/swag/cmd/swag@latest
# go get -u github.com/swaggo/files
# go get -u github.com/swaggo/gin-swagger
# swag -h
NAME:
swag - Automatically generate RESTful API documentation with Swagger 2.0 for Go.
USAGE:
swag [global options] command [command options] [arguments...]
VERSION:
v1.8.4
COMMANDS:
init, i Create docs.go
fmt, f format swag comments
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--version, -v print the version (default: false)
main.go 는 아래와 같다.
package main
import (
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
router.Run(":8080")
}
swag init는 API에 대한 문서를 업데이트할 때마다 실행 합니다.
# swag init
2022/08/08 17:59:19 Generate swagger docs....
2022/08/08 17:59:19 Generate general API Info, search dir:./
2022/08/08 17:59:19 create docs.go at docs/docs.go
2022/08/08 17:59:19 create swagger.json at docs/swagger.json
2022/08/08 17:59:19 create swagger.yaml at docs/swagger.yaml
실행을 하면 docs 하위에 swagger 문서가 생성된다.
# tree docs
docs
├── docs.go
├── swagger.json
└── swagger.yaml
0 directories, 3 files
한번 docs 를 호출해 보자.
아직 swagger 관련 내용이 없어 error가 발생한다. 아래와 같이 main.go 를 수정한다.
package main
import (
_ "github.com/kmaster8/gin/myproject/docs"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/hello/:name", hello)
r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
return r
}
// Hello godoc
// @Summary hello name
// @Description 이름을 리턴한다.
// @Accept json
// @Produce json
// @Param name path string true "Name"
// @Success 200
// @Router /hello/{name} [get]
func hello(c *gin.Context) {
name := c.Param("name")
c.JSON(200, gin.H{"name ": name})
}
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /
func main() {
router := setupRouter()
router.Run(":8080")
}
[실행결과]
참고
'개발 > go' 카테고리의 다른 글
GoDS ( Go Data Structures ) (0) | 2022.08.24 |
---|---|
Viper (0) | 2022.08.23 |
cobra library 사용법 (0) | 2022.08.22 |
모둘과 패키지 (0) | 2022.08.22 |
Kubebuilder ( Kubernetes operator ) (0) | 2022.08.17 |