Skip to main content

HTTP

This chapter introduces the complete process of HTTP request and response.

Introduction​

In the HTTP request-response process, from the request reaching the service node, to process handling, and finally responding to the requester, each step will be processed as needed.

Request​

Route Middleware​

Route middleware often plays the role of filtering, intercepting, and context management in the entire request call chain. It is commonly used for authentication and other purposes, and is typically positioned at the first location after the request arrives, before the handler function.

Now, let's implement a simple authentication middleware.

examples/pkg/app/user/http/middleware/authcheck.go
import (
"github.com/gin-gonic/gin"
"github.com/keepchen/go-sail/v3/constants"
)
func AuthCheck() gin.HandlerFunc {
return func(c *gin.Context) {
authorization := c.GetHeader("Authorization")
if len(authorization) == 0 {
sail.Response(c).Builder(constants.ErrAuthorizationTokenInvalid, nil).Send()
return
}

uid := parseUserIDFromAuthorization(authorization)
c.Set("userID", uid)
c.Next()
}
}

func parseUserIDFromAuthorization(authorization string) int64 {
// TODO
return int64(123)
}

Register it to the routes.

examples/pkg/app/user/http/routes/routes.go
import (
"github.com/gin-gonic/gin"
"examples/pkg/app/user/http/middleware"
"examples/pkg/app/user/http/handler"
)

func RegisterRoutes(r *gin.Engine) {
userGroup := r.Group("/user", middleware.AuthCheck())
{
userGroup.GET("info", handler.UserInfo)
}
}

Entity​

The request entity defines the data structure of request parameters, which provides clear data types for subsequent parameter processing. Therefore, we recommend defining it explicitly. We also define its extended validation method to verify whether the internal data meets the requirements.

examples/pkg/app/user/http/vo/request/userinforeqvo.go
import (
sailConstants "github.com/keepchen/go-sail/v3/constants"
)
type UserInfoReqVO struct {
ShowDetail bool `json:"showDetail" form:"showDetail" validate:"required" format:"bool" `
WithWalletInfo bool `json:"withWalletInfo" form:"withWalletInfo" validate:"required" format:"bool" `
}

func (v *UserInfoReqVO) Validator() (sailConstants.ICodeType, error) {
return sailConstants.ErrNone, nil
}

Business Logic​

Parameter Binding​

examples/pkg/app/user/http/handler/user.go
import (
"go.uber.org/zap"
"github.com/gin-gonic/gin"
sailConstants "github.com/keepchen/go-sail/v3/constants"
"github.com/keepchen/go-sail/v3/sail"
"examples/pkg/app/user/http/vo/request"
)

func UserInfo(c *gin.Context) {
var (
...
form request.UserInfoReqVO
loggerSvc = sail.LogTrace(c).GetLogger()
userID = c.MustGet("userID").(int64)
...
)
if err := c.ShouldBind(&form); err != nil {
sail.Response(c).Failure400(sailConstants.ErrRequestParamsInvalid)
return
}
if code, err := form.Validator(); err != nil {
loggerSvc.Warn("[UserInfo] form field validate failed", zap.Errors("errors", []error{err}))
sail.Response(c).Failure400(code)
return
}
...
}

Parameter Validation​

examples/pkg/app/user/http/handler/user.go
import (
"go.uber.org/zap"
"github.com/gin-gonic/gin"
sailConstants "github.com/keepchen/go-sail/v3/constants"
"github.com/keepchen/go-sail/v3/sail"
"examples/pkg/app/user/http/vo/request"
)

func UserInfo(c *gin.Context) {
var (
...
form request.UserInfoReqVO
loggerSvc = sail.LogTrace(c).GetLogger()
userID = c.MustGet("userID").(int64)
...
)
if err := c.ShouldBind(&form); err != nil {
sail.Response(c).Failure400(sailConstants.ErrRequestParamsInvalid)
return
}
if code, err := form.Validator(); err != nil {
loggerSvc.Warn("[UserInfo] form field validate failed", zap.Errors("errors", []error{err}))
sail.Response(c).Failure400(code)
return
}
...
}

Handler Function​

examples/pkg/app/user/http/handler/user.go
import (
"go.uber.org/zap"
"github.com/gin-gonic/gin"
sailConstants "github.com/keepchen/go-sail/v3/constants"
"github.com/keepchen/go-sail/v3/sail"
"examples/pkg/app/user/http/vo/request"
)

func UserInfo(c *gin.Context) {
var (
...
form request.UserInfoReqVO
loggerSvc = sail.LogTrace(c).GetLogger()
userID = c.MustGet("userID").(int64)
...
)
if err := c.ShouldBind(&form); err != nil {
sail.Response(c).Failure400(sailConstants.ErrRequestParamsInvalid)
return
}
if code, err := form.Validator(); err != nil {
loggerSvc.Warn("[UserInfo] form field validate failed", zap.Errors("errors", []error{err}))
sail.Response(c).Failure400(code)
return
}

var user User
sail.GetDBR().Model(&User{}).Where("id = ?", userID).First(&user)
...
}

Response​

Entity​

The response entity defines the data structure of the returned data, which will provide clear data types for subsequent response processing. Therefore, we recommend defining it explicitly.

examples/pkg/app/user/http/vo/response/userinfoackvo.go
import (
sailConstants "github.com/keepchen/go-sail/v3/constants"
)

type UserInfoAckVO struct {
UserID int64 `json:"userId" format:"number" validate:"required" example:"123"`
Email string `json:"email" format:"string" validate:"required" example:"go-sail@example.com"`
Nickname string `json:"nickname" format:"string" validate:"required" example:"go-sail"`
}

Response​

examples/pkg/app/user/http/handler/user.go
import (
"go.uber.org/zap"

sailConstants "github.com/keepchen/go-sail/v3/constants"
"github.com/keepchen/go-sail/v3/sail"
"examples/pkg/app/user/http/vo/request"
"examples/pkg/app/user/http/vo/response"
)

func UserInfo(c *gin.Context) {
var (
...
form request.UserInfoReqVO
resp resp.UserInfoAckVO
loggerSvc = sail.LogTrace(c).GetLogger()
userID = c.MustGet("userID").(int64)
...
)
if err := c.ShouldBind(&form); err != nil {
sail.Response(c).Failure400(sailConstants.ErrRequestParamsInvalid)
return
}
if code, err := form.Validator(); err != nil {
loggerSvc.Warn("[UserInfo] form field validate failed", zap.Errors("errors", []error{err}))
sail.Response(c).Failure400(code)
return
}

var user User
sail.GetDBR().Model(&User{}).Where("id = ?", userID).First(&user)
...
resp.UserID = user.ID
resp.Email = user.Email
resp.Nickname = user.Nickname

sail.Response(c).Data(resp)
}

Syntactic Sugar​

Syntactic sugar provides richer response functions.

sail.Response(c).Data(nil)

sail.Response(c).Success()

sail.Response(c).Failure()

sail.Response(c).Failure("failed")

sail.Response(c).Failure400()

sail.Response(c).Failure500()

sail.Response(c).Wrap(constants.XXX, anyValue, "SUCCESS").Send()

API Options​

API options allow developers to customize the behavior of the response handler.
These options are set when the framework starts:

main.go
import (
"github.com/keepchen/go-sail/v3/constants"
"github.com/keepchen/go-sail/v3/http/api"
"github.com/keepchen/go-sail/v3/sail/config"
)

const ErrNone = sailConstants.CodeType(200)

var (
conf = &config.Config{}
option = &api.Option{
Timezone: constants.DefaultTimeZone,
ErrNoneCode: ErrNone,
ErrNoneCodeMsg: "SUCCESS",
ForceHttpCode200: true,
}
)

func main() {
sail.
WakeupHttp("go-sail", conf).
SetupApiOption(option).
Hook(registerRoutes, nil, nil).
Launch()
}

Error Codes​

The Go-Sail framework provides only a few error code constants. During business development, developers need to register their own error codes and error messages. Go-Sail's error code registration functionality supports internationalization. Developers need to follow the specification for registration to take effect.

Registration​

Here's a code example:

examples/pkg/constants/errors.go
import (
"sync"
sailConstants "github.com/keepchen/go-sail/v3/constants"
)

const (
ErrNone = sailConstants.CodeType(200)
ErrStatusGatewayTimeoutTimeOut = sailConstants.CodeType(504)
ErrInternalSeverError = sailConstants.CodeType(999999)
ErrRequestParamsInvalid = sailConstants.CodeType(100000)
ErrAuthorizationTokenInvalid = sailConstants.CodeType(100001)
SliderValidationFailed = sailConstants.CodeType(8026)
)

var codeMsgMap = map[sailConstants.LanguageCode]map[sailConstants.ICodeType]string{
//English
sailConstants.LanguageEnglish: {
ErrNone: "SUCCESS",
ErrStatusGatewayTimeoutTimeOut: "Timeout",
ErrInternalSeverError: "Internal server error",
ErrRequestParamsInvalid: "Bad request parameters",
ErrAuthorizationTokenInvalid: "Token invalid",
SliderValidationFailed: "Slider validation failed",
},
//more...
}

var once sync.Once

func init() {
once.Do(func() {
go func() {
time.Sleep(time.Second * 5)
for lang, msgMap := range codeMsgMap {
sailConstants.RegisterCodeTable(lang, msgMap)
}
}()
})
}

Usage​

Now you can use them to respond:

examples/pkg/app/user/http/handler/user.go
import (
"github.com/gin-gonic/gin"
"github.com/keepchen/go-sail/v3/sail"
"examples/pkg/constants"
)

func UserInfo(c *gin.Context) {
...
sail.Response(c).Failure200(constants.SliderValidationFailed)
}