2019. 12. 11. 18:30 ㆍ개발 이야기/Golang
틈틈이 Go언어를 공부하면서
간단한 웹서비스를 구축 중에 있는데요!
바로 Go 언어로 만들어진 웹 프레임워크
gin framework를 이용해서 만들고 있습니다
gin 프레임워크는
쉽게 파이썬의 flask, django와 같은 웹 프레임워크입니다!
gin을 사용하는 이유는
위와 같이 go 웹 프레임워크 중 가장 많은 스타를 가지고 있고
검색을 하면 가장 먼저 나오기 때문입니다.
저는 이 프레임워크를 이용해서 간단한 웹서비스를 구축해보겠습니다.
먼저 설명을 한번 보겠습니다.
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
깃허브에 나와있는 부분인데
요렇게 복붙을 하고 빌드를 한번 해보겠습니다.
컴파일 후 실행하면 아래와 같이 만들어 놓은 api가 쭉 ~ 나열되고
일단 방금 만든 /ping에 postman을 이용해서 리퀘스트를 날려보겠습니다!
postman을 보면
reponse에 pong으로 응답이 온 것을 확인할 수 있습니다.
자 그럼 간략하게 DB에 연결해서
바이너리를 실행했을 때 health check 용으로
만들 Time api를 만들어 보겠습니다.
Time api는 간단하게 리퀘스트를 하면
서버의 현재 시간을 리턴해주는 api인데요!
물론 go 언어에도 time 함수가 있지만 DB에 연결하는 이유는
health 체크를 할 때 DB 커넥션까지 확인할 수 있기 때문입니다!
자 먼저 route를 추가해줍니다!
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/Time", api.Time)
r.Run(":5000")
}
그리곤 Time 함수를 만들어 주면 되는데요!
그전에 할 것이 있습니다
먼저 말했듯, Time함수에서 가져오는 시간은 DB까지 연결해서 select 문을 이용해 시간을 가져올 건데요!
DB를 연동하는 함수를 먼저 만들어 보겠습니다!
func ConnectToDb() (*sql.DB, error) {
db, err := sql.Open("postgres",
"user=유저네임 password=비밀번호 dbname=디비명 host=IP주소 sslmode=disable")
if db != nil{
db.SetMaxOpenConns(100) // 최대 커넥션 개수를 제한
db.SetMaxIdleConns(10) // 대기 커넥션 최대 개수를 설정
}
if err != nil {
return nil, err
}
return db, err
}
Go 언어의 많은 부분 작성하게 되는 부분이 바로 error 처리인데요
DB가 오픈이 되지 않는 다면 에러를 반환하고
오픈이 정상적으로 되면 DB 구초제의 포인터를 반환하게 됩니다!
DB 구조체는 아래와 같이 생겼는데요!
type DB struct {
// Atomic access only. At top of struct to prevent mis-alignment
// on 32-bit platforms. Of type time.Duration.
waitDuration int64 // Total time waited for new connections.
connector driver.Connector
// numClosed is an atomic counter which represents a total number of
// closed connections. Stmt.openStmt checks it before cleaning closed
// connections in Stmt.css.
numClosed uint64
mu sync.Mutex // protects following fields
freeConn []*driverConn
connRequests map[uint64]chan connRequest
nextRequest uint64 // Next key to use in connRequests.
numOpen int // number of opened and pending open connections
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{}
resetterCh chan *driverConn
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdle int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
maxIdleClosed int64 // Total number of connections closed due to idle.
maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
stop func() // stop cancels the connection opener and the session resetter.
}
func OpenDB(c driver.Connector) *DB {
ctx, cancel := context.WithCancel(context.Background())
db := &DB{
connector: c,
openerCh: make(chan struct{}, connectionRequestQueueSize),
resetterCh: make(chan *driverConn, 50),
lastPut: make(map[*driverConn]string),
connRequests: make(map[uint64]chan connRequest),
stop: cancel,
}
go db.connectionOpener(ctx)
go db.connectionResetter(ctx)
return db
}
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
Go 언어에는 C, C++와 같이 포인터 개념이 존재하기에 포인터로 리턴을 해줍니다
(포인터를 사용하면 리소스 사용이 작아지겠죠 ? 리소스를 위해서 포인터 리턴)
자 이렇게 정상적으로 DB 포인터 리턴이 완료되면
이 구초제를 이용해서 쿼리를 날릴 수 있습니다.
func Time(c *gin.Context) {
db, err := model.ConnectToDb()
if err != nil {
log.Fatal(err)
}
defer db.Close()
var time string
err = db.QueryRow("" +
"SELECT now()").Scan(&time)
if err != nil {
log.Println(err)
}
c.JSON(http.StatusOK, map[string]string{
"Time": time,
})
}
ConnectToDb 함수를 이용해 DB 포인터를 받습니다.
그다음에
"""
defer db.Close()
"""
를 볼 수 있는데요 Go언어에는 defer라는 것이 존재하는데요
(다른 언어에도 존재하는 개념인지는 모르겠습니다)
이 defer는 지연 실행 개념으로
정확히는 해당 함수가 리턴되기 전에 실행하라는 의미입니다
그래서 결과적으로 Time 함수가 종료되기 직전에
db.close() 메서드를 실행하라는 의미죠!
DB 커넥션을 했으면 끝나면 종료해주는 게 맞죠!
그리고 db.QueryRow() 메서드를 이용해
time이라는 string 변수에 담겠습니다.
var time string
err = db.QueryRow("" +
"SELECT now()").Scan(&time)
if err != nil {
log.Println(err)
}
c.JSON(http.StatusOK, map[string]string{
"Time": time,
})
. Scan 메서드를 이용해 time 변수에 값을 넣어줍니다
그리고 JSON 방식으로 reqonse를 날려줍니다.
이렇게 하면 DB와 연결된
heath 체크용! Time api를 완성했습니다.
이후에는 간단하게 계정 CRUD를 만들어보도록 하겠습니다~
'개발 이야기 > Golang' 카테고리의 다른 글
#6 Golang gomail을 이용해 SMTP 연동하기 (0) | 2020.01.27 |
---|---|
#5 Golang bcrypt로 비밀번호 해싱 및 검증하기 (0) | 2019.12.30 |
#4 Golang 인증 middleware 구현하기(gin framework) (0) | 2019.12.20 |
#3 Golang JWT 토큰으로 인증 구현하기 (0) | 2019.12.16 |
#1 Golang 이란? (0) | 2019.12.11 |