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:"[email protected]"`
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)
}