A dij-style gin library. gin is one of most popular web frameworks for golang. dij stands for dependency injection. This library provides a dij-style gin wrapper.
Refer to completed examples in go-examples.
- Gin Style
- dij-gin Style
- Query
- Where variable data came from?
- Customize path name and http method
- Body
- Form
- Json
- Validator
- Response
- Middlewares
- OpenAPI generation
- tag/group
- Runtime environment
- Http tag
- TODO List
The WebContext embeds gin.Context, any gin helper functions can be used directly.
package main
import (
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
}
// GetHello a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello
func (s *TWebServer) GetHello(ctx WebContext) {
ctx.IndentedJSON(http.StatusOK, "/hello")
}
func main() {
//dij.EnableLog()
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
The function name GetHello should combine a method and a path name. Method should be one of valid http methods, for examples: Get, Post, Delete, etc.
You can group a few http functions in one controller, which likes what gin.router.Group function does.
package main
import (
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
userCtl *TUserController `di:""` // inject dependency by class/default name
}
type TUserController struct {
WebController `http:"user"` //group by 'user' path
}
// Get a http request with "get" method.
// Url should like this in local: http://localhost:8000/user
func (u *TUserController) Get(ctx WebContext) {
ctx.IndentedJSON(http.StatusOK, "/user")
}
// GetMe a http request with "get" method.
// Url should like this in local: http://localhost:8000/user/me
func (u *TUserController) GetMe(ctx WebContext) {
ctx.IndentedJSON(http.StatusOK, "/user/me")
}
func main() {
//dij.EnableLog()
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
dij-gin style includes many features:
- Easy way to retrieve path parameters, query parameters, etc.
- Easy way to response data with different http code.
- Support OpenAPI(v3.0) generation.
- Http functions support to enable or disable by runtime environment, aka: prod, dev, and test.
package main
import (
"fmt"
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
}
// GetHello a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello?name=wayne&age=123.
// The result will be:
//
// "/hello wayne, 123 years old"
func (s *TWebServer) GetHello(ctx struct {
WebContext
Name string `http:"name"`
Age int `http:"age"`
}) {
ctx.IndentedJSON(http.StatusOK, fmt.Sprintf("/hello %s, %d years old", ctx.Name, ctx.Age))
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
Add an attribute "in=xxx" in http tag. About http tag setting, see the reference
package main
import (
"fmt"
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
userCtl *TUserController `di:""` // inject dependency by class/default name
}
type TUserController struct {
WebController `http:"user"` //group by 'user' path
}
// PutUserById a http request with "get" method.
// Curl this url should like this in local:
//
// curl -d "age=34&name=wayne" -X PUT http://localhost:8000/user/2345/profile
//
// The result should be:
//
// "update user(#2345)'s name wayne and age 34"
func (u *TUserController) PutUserById(ctx struct {
WebContext `http:":id/profile"`
Id int `http:"id,in=path"`
Name string `http:"name,in=body"`
Age int `http:"age,in=body"`
}) {
ctx.IndentedJSON(http.StatusOK, fmt.Sprintf("update user(#%d)'s name %s and age %d", ctx.Id, ctx.Name, ctx.Age))
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
Add http tag with the format "[path],method=[method]".
package main
import (
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
}
// SayHi a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello.
func (s *TWebServer) SayHi(ctx struct {
WebContext `http:"hello,method=get"`
}) {
ctx.IndentedJSON(http.StatusOK, "hello")
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
package main
import (
. "github.com/letscool/dij-gin"
"log"
)
type TWebServer struct {
WebServer
}
// NoRoute is entry for page not found, is only supported in root path currently.
// Any query will show the log.
func (s *TWebServer) NoRoute(ctx WebContext) {
log.Printf("no route: %s\n", ctx.Request.RequestURI)
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
dij-gin uses go-playground/validator/v10 for validation. gin uses 'binding' as validation tag key instead of 'validate', 'validate' tag key is chose from go-playground/validator/v10 official. dij-gin still uses 'validate' tag key.
package main
import (
"fmt"
. "github.com/letscool/dij-gin"
"log"
"net/http"
)
type TWebServer struct {
WebServer
userCtl *TUserController `di:""` // inject dependency by class/default name
}
type TUserController struct {
WebController `http:"user"` //group by 'user' path
}
// GetUserById a http request with "get" method.
// Url should like this in local: http://localhost:8000/user/2345/profile.
// The result will be:
//
// {"message":"Key: 'Id' Error:Field validation for 'Id' failed on the 'lte' tag","code":"400"}
func (u *TUserController) GetUserById(ctx struct {
WebContext `http:":id/profile"`
Id int `http:"id,in=path" validate:"gte=100,lte=999"`
}) {
ctx.IndentedJSON(http.StatusOK, fmt.Sprintf("get user(#%d)'s profile", ctx.Id))
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
package main
import (
"errors"
. "github.com/letscool/dij-gin"
"log"
)
type TWebServer struct {
WebServer
}
// GetResp a http request with "get" method.
// Url should like this in local: http://localhost:8000/resp?select=1 .
// Use *curl -v* command to see response code.
func (s *TWebServer) GetResp(ctx struct {
WebContext
Select int `http:"select"`
}) (result struct {
Ok200 *string // the range of last three characters is between 2xx and 5xx, so the response code = 200
Ok *string `http:"201"` // force response code to 201
Redirect302 *string // redirect data should be string type, because it is redirect location.
Error error // default response code for error is 400
}) {
switch ctx.Select {
case 1:
data := "ok"
result.Ok200 = &data
case 2:
data := "ok"
result.Ok = &data
case 3:
url := "https://github.com/letscool"
result.Redirect302 = &url
default:
result.Error = errors.New("an error")
}
return
}
func main() {
if err := LaunchGin(&TWebServer{}); err != nil {
log.Fatalln(err)
}
}
- Log all http methods for a controller and all it's sub-controllers
package main
import (
"fmt"
"github.com/gin-gonic/gin"
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"log"
"net/http"
)
type TWebServer struct {
WebServer `http:",middleware=log"`
_ *libs.LogMiddleware `di:""`
}
// GetHello a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello
func (t *TWebServer) GetHello(ctx WebContext) {
ctx.IndentedJSON(http.StatusOK, "/hello")
}
func main() {
//f, _ := os.Create("gin.log") // log to file
config := NewWebConfig().
//SetDefaultWriter(io.MultiWriter(f)).
SetDependentRef(libs.RefKeyForLogFormatter, (gin.LogFormatter)(func(params gin.LogFormatterParams) string {
// your custom format
return fmt.Sprintf("[%s-%s] \"%s %s\"\n",
params.ClientIP,
params.TimeStamp.Format("15:04:05.000"),
params.Method,
params.Path,
)
}))
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
- Log functions only which set log middleware
package main
import (
"fmt"
"github.com/gin-gonic/gin"
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"log"
"net/http"
)
type TWebServer struct {
WebServer `http:""`
_ *libs.LogMiddleware `di:""`
}
// GetHelloWithLog a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello_with_log
func (t *TWebServer) GetHelloWithLog(ctx struct {
WebContext `http:"hello_with_log,middleware=log"`
}) {
ctx.IndentedJSON(http.StatusOK, "hello with log")
}
// GetHelloWithoutLog a http request with "get" method.
// Url should like this in local: http://localhost:8000/hello_without_log
func (t *TWebServer) GetHelloWithoutLog(ctx struct {
WebContext `http:"hello_without_log"`
}) {
ctx.IndentedJSON(http.StatusOK, "hello without log")
}
func main() {
//f, _ := os.Create("gin.log") // log to file
config := NewWebConfig().
//SetDefaultWriter(io.MultiWriter(f)).
SetDependentRef(libs.RefKeyForLogFormatter, (gin.LogFormatter)(func(params gin.LogFormatterParams) string {
// your custom format
return fmt.Sprintf("[%s-%s] \"%s %s\"\n",
params.ClientIP,
params.TimeStamp.Format("15:04:05.000"),
params.Method,
params.Path,
)
}))
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
This sample includes a middleware basic_auth_middleware.go and a fake account db account.go, and also enables OpenAPI document.
package main
import (
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"github.com/letscool/go-examples/dij-gin/shared"
"log"
)
type TWebServer struct {
WebServer
_ *libs.SwaggerController `di:""` // Bind OpenApi controller in root.
_ *TUserController `di:""`
}
type TUserController struct {
WebController `http:"user"`
_ *shared.BasicAuthMiddleware `di:""`
}
// GetMe a http request with "get" method.
// Url should like this in local: http://localhost:8000/user/me.
// And login with username "john" and password "abc".
func (u *TUserController) GetMe(ctx struct {
WebContext `http:",middleware=basic_auth" security:"basic_auth_1"`
}) (result struct {
Account *shared.Account `http:"200,json"`
}) {
result.Account = ctx.MustGet(shared.BasicAuthUserKey).(*shared.Account)
return
}
func main() {
ac := &shared.FakeAccountDb{} // This object should implement shared.BasicAuthAccountCenter interface.
ac.InitFakeDb()
config := NewWebConfig().
SetDependentRef(shared.RefKeyForBasicAuthAccountCenter, ac).
SetOpenApi(func(o *OpenApiConfig) {
o.Enable().UseHttpOnly().SetDocPath("doc").
AppendBasicAuth("basic_auth_1")
})
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
This sample includes a middleware bearer_middleware.go and a fake account db account.go, and also enables OpenAPI document.
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"github.com/letscool/go-examples/dij-gin/shared"
"log"
"time"
)
type TWebServer struct {
WebServer
_ *libs.SwaggerController `di:""` // Bind OpenApi controller in root.
_ *TUserController `di:""`
}
type TUserController struct {
WebController `http:"user"`
_ *shared.BearerMiddleware `di:""`
}
// GetMe a http request with "get" method.
// Url should like this in local: http://localhost:8000/user/me.
// And login with username "john" and password "abc".
func (u *TUserController) GetMe(ctx struct {
WebContext `http:",middleware=bearer" security:"bearer_1"`
}) (result struct {
Account *shared.Account `http:"200,json"`
}) {
result.Account = ctx.MustGet(shared.BearerUserKey).(*shared.Account)
return
}
func main() {
ac := &shared.FakeAccountDb{} // This object must implement shared.BearerValidator interface.
accounts := ac.InitFakeDb()
// generate a jwt token for test
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "dij-gin-samples",
ID: accounts[0].User,
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString([]byte(ac.BearerSecret()))
if err != nil {
log.Panicln(err)
} else {
fmt.Println("*************************")
fmt.Printf("User: %s\n", accounts[0].User)
fmt.Printf("Bearer token: %s\n", tokenString)
fmt.Println("*************************")
}
//
config := NewWebConfig().
SetDependentRef(shared.RefKeyForBearerValidator, ac).
SetOpenApi(func(o *OpenApiConfig) {
o.Enable().UseHttpOnly().SetDocPath("doc").
AppendBearerAuth("bearer_1")
})
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
This sample also enables OpenAPI document.
package main
import (
"errors"
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"log"
)
type TWebServer struct {
WebServer
_ *libs.SwaggerController `di:""` // Bind OpenApi controller in root.
_ *TUserController `di:""`
}
type TUserController struct {
WebController `http:"user"`
}
// GetMe a http request with "get" method.
// Url should like this in local: http://localhost:8000/user/me.
// And login with username "john" and password "abc".
func (u *TUserController) GetMe(ctx struct {
WebContext `http:"" security:"ApiId,ApiKey" description:"Set ApiId=abc and ApiKey=EFG"`
apiId string `http:"api_id"`
apiKey string `http:"api_key"`
}) (result struct {
Message *string `http:"200"`
Error error `http:"401"`
}) {
if ctx.apiId == "abc" && ctx.apiKey == "EFG" {
msg := "You got permission to access this api"
result.Message = &msg
} else {
result.Error = errors.New("unauthorized api key")
}
return
}
func main() {
// launch a web server
config := NewWebConfig().
SetOpenApi(func(o *OpenApiConfig) {
o.Enable().UseHttpOnly().SetDocPath("doc").
AppendApiKeyAuth("ApiId", "query", "api_id").
AppendApiKeyAuth("ApiKey", "query", "api_key")
})
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
(on-going)
When you use dij-gin style to setup server, dij-gin server will automatically generate OpenAPI document if you need.
// Copyright 2022 Yuchi Chen. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package main
import (
"errors"
. "github.com/letscool/dij-gin"
"github.com/letscool/dij-gin/libs"
"log"
)
type TWebServer struct {
WebServer
openapi *libs.SwaggerController `di:""` // Bind OpenApi controller in root.
}
// GetResp a http request with "get" method.
// Url should like this in local: http://localhost:8000/resp?select=1 .
// Use *curl -v* command to see response code.
func (s *TWebServer) GetResp(ctx struct {
WebContext
Select int `http:"select"`
}) (result struct {
Ok200 *string // the range of last three characters is between 2xx and 5xx, so the response code = 200
Ok *string `http:"201"` // force response code to 201
Error error // default response code for error is 400
}) {
switch ctx.Select {
case 1:
data := "ok"
result.Ok200 = &data
case 2:
data := "ok"
result.Ok = &data
default:
result.Error = errors.New("an error")
}
return
}
// The OpenAPI page will be enabled in location: http://localhost:8000/doc.
func main() {
config := NewWebConfig().
SetOpenApi(func(o *OpenApiConfig) {
o.SetEnabled(true).UseHttpOnly().SetDocPath("doc")
})
if err := LaunchGin(&TWebServer{}, config); err != nil {
log.Fatalln(err)
}
}
(on-going)
- path
- name
- method
- env
- tag
- middleware
The http tag includes an attribute "[AttrKey]" for request and response body. and "mime=[MIME_TYPE]" for response body only.
AttrKey | Req/Resp | MIME Type |
---|---|---|
form, multipart | Req | multipart/form-data |
urlenc, urlencoded | BOTH | application/x-www-form-urlencoded |
json | Both | application/json |
xml | Both | application/xml |
plain | Resp | text/plain |
page, html | Resp | text/html |
octet | Resp | application/octet-stream |
jpeg, png | Resp | image/jpeg,png |
The http tag includes an attribute "in=[AttrKey]"
AttrKey | Default situation | Meaning |
---|---|---|
header | ||
cookie | ||
path | If variable name is included in path | |
query | ||
body |
Still many function should be implemented, such as:
- Redirect response
- Response urlencoded data
- More middlewares
- Fix bugs
- More examples for http tag settings
- Add unit tests
- Dynamic path for controller
- NoRoute