English | 中文

Introduction
orbit
is a lightweight HTTP web service wrapper framework. It is designed to be simple and easy to use, providing a series of convenient features to help you quickly build a web service.
Why is it called orbit
? It provides a framework that encapsulates the complexities of building a web service, allowing you to focus on your core business logic. With orbit
, you can easily develop and maintain your web service, just like a satellite smoothly orbiting the Earth.
Why not use gin
directly? While gin
is a great framework, starting a web service with it requires additional work such as logging and monitoring. orbit
is built on top of gin
and offers these features out of the box.
Advantages
- Lightweight and easy to use
- Supports
zap
logging with both async
and sync
modes
- Provides
prometheus
monitoring integration
- Includes
swagger
API documentation support
- Enables graceful shutdown of the server
- Supports
cors
middleware for handling cross-origin requests
- Automatically recovers from panics
- Allows customization of middleware
- Supports custom router groups
- Provides flexibility in defining access log format and fields
- Offers repeat read of request/response body and caching of body bytes
Installation
go get github.com/shengyanli1982/orbit
Quick Start
orbit
is incredibly easy to use, allowing you to quickly build a web service in just a few minutes. The typical steps involved are:
- Create the
orbit
start configuration.
- Define the
orbit
feature options.
- Create an instance of
orbit
.
Default URL Paths
[!NOTE]
The default URL paths are system-defined and cannot be changed.
/metrics
- Prometheus metrics
/swagger/*any
- Swagger API documentation
/debug/pprof/*any
- PProf debug
/ping
- Health check
1. Configuration
orbit
provides several configuration options that can be set before starting the orbit
instance.
WithSugaredLogger
- Use zap
sugared logger (default: DefaultSugaredLogger
).
WithLogger
- Use zap
logger (default: DefaultConsoleLogger
).
WithAddress
- HTTP server listen address (default: 127.0.0.1
).
WithPort
- HTTP server listen port (default: 8080
).
WithRelease
- HTTP server release mode (default: false
).
WithHttpReadTimeout
- HTTP server read timeout (default: 15s
).
WithHttpWriteTimeout
- HTTP server write timeout (default: 15s
).
WithHttpReadHeaderTimeout
- HTTP server read header timeout (default: 15s
).
WithAccessLogEventFunc
- HTTP server access log event function (default: DefaultAccessEventFunc
).
WithRecoveryLogEventFunc
- HTTP server recovery log event function (default: DefaultRecoveryEventFunc
).
WithPrometheusRegistry
- HTTP server Prometheus registry (default: prometheus.DefaultRegister
).
You can use NewConfig
to create a default configuration and WithXXX
methods to set the configuration options. DefaultConfig
is an alias for NewConfig()
.
2. Features
orbit
provides several feature options that can be set before starting the orbit
instance:
EnablePProf
- enable pprof debug (default: false
)
EnableSwagger
- enable swagger API documentation (default: false
)
EnableMetric
- enable Prometheus metrics (default: false
)
EnableRedirectTrailingSlash
- enable redirect trailing slash (default: false
)
EnableRedirectFixedPath
- enable redirect fixed path (default: false
)
EnableForwardedByClientIp
- enable forwarded by client IP (default: false
)
EnableRecordRequestBody
- enable record request body (default: false
)
You can use NewOptions
to create a null feature, and use EnableXXX
methods to set the feature options.
DebugOptions
is used for debugging and is an alias of NewOptions().EnablePProf().EnableSwagger().EnableMetric().EnableRecordRequestBody()
.
ReleaseOptions
is used for release and is an alias of NewOptions().EnableMetric()
.
[!NOTE]
It is recommended to use DebugOptions
for debugging and ReleaseOptions
for release.
3. Creating an Instance
Once you have created the orbit
configuration and feature options, you can create an orbit
instance.
[!IMPORTANT]
When you run the orbit
instance, it will not block the current goroutine. This means you can continue doing other things after running the orbit
instance.
If you want to block the current goroutine, you can use the project GS
to provide a Waitting
function to block the current goroutine.
[!TIP]
To simplify the process, you can use NewHttpService
to wrap the func(*gin.RouterGroup)
into an implementation of the Service
interface.
NewHttpService(func(g *gin.RouterGroup) {
g.GET("/demo", func(c *gin.Context) {
c.String(http.StatusOK, "demo")
})
})
Example
package main
import (
"time"
"github.com/shengyanli1982/orbit"
)
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项
// Create a new Orbit feature options
opts := orbit.NewOptions()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
[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 --> github.com/shengyanli1982/orbit.healthcheckService.func1 (1 handlers)
{"level":"INFO","time":"2024-01-10T17:00:13.139+0800","logger":"default","caller":"orbit/gin.go:160","message":"http server is ready","address":"127.0.0.1:8080"}
Testing
$ curl -i http://127.0.0.1:8080/ping
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 10 Jan 2024 09:07:26 GMT
Content-Length: 7
successs
4. Custom Middleware
orbit
is based on gin
, so you can directly use gin
middleware. This allows you to implement custom middleware for specific tasks. For example, you can use the demo
middleware to print >>>>>>!!! demo
in the console.
Here is an example of using custom middleware in orbit
:
Example
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
)
// customMiddleware 函数定义了一个自定义的中间件
// The customMiddleware function defines a custom middleware
func customMiddleware() gin.HandlerFunc {
// 返回一个 Gin 的 HandlerFunc
// Return a Gin HandlerFunc
return func(c *gin.Context) {
// 打印一条消息
// Print a message
fmt.Println(">>>>>>!!! demo")
// 调用下一个中间件或处理函数
// Call the next middleware or handler function
c.Next()
}
}
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo" path
g.GET("/demo", func(c *gin.Context) {})
}
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项,并启用 metric
// Create a new Orbit feature options and enable metric
opts := orbit.NewOptions().EnableMetric()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的中间件
// Register a custom middleware
engine.RegisterMiddleware(customMiddleware())
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
$ go run demo.go
[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 --> github.com/shengyanli1982/orbit.healthcheckService.func1 (1 handlers)
[GIN-debug] GET /metrics --> github.com/shengyanli1982/orbit/utils/wrapper.WrapHandlerToGin.func1 (2 handlers)
[GIN-debug] GET /demo --> main.(*service).RegisterGroup.func1 (7 handlers)
{"level":"INFO","time":"2024-01-10T20:03:38.869+0800","logger":"default","caller":"orbit/gin.go:162","message":"http server is ready","address":"127.0.0.1:8080"}
>>>>>>!!! demo
{"level":"INFO","time":"2024-01-10T20:03:41.275+0800","logger":"default","caller":"log/default.go:10","message":"http server access log","id":"","ip":"127.0.0.1","endpoint":"127.0.0.1:59787","path":"/demo","method":"GET","code":200,"status":"OK","latency":"780ns","agent":"curl/8.1.2","query":"","reqContentType":"","reqBody":""}
5. Custom Router Group
The custom router group feature in orbit
allows you to register a custom router group for the demo
service. For example, you can register routes like /demo
and /demo/test
.
Example
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
ocom "github.com/shengyanli1982/orbit/common"
)
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 注册一个自定义的路由组 "/demo"
// Register a custom router group "/demo"
g = g.Group("/demo")
// 在 "/demo" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo" path
g.GET(ocom.EmptyURLPath, func(c *gin.Context) {
// 返回 HTTP 状态码 200 和 "demo" 字符串
// Return HTTP status code 200 and the string "demo"
c.String(http.StatusOK, "demo")
})
// 在 "/demo/test" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo/test" path
g.GET("/test", func(c *gin.Context) {
// 返回 HTTP 状态码 200 和 "test" 字符串
// Return HTTP status code 200 and the string "test"
c.String(http.StatusOK, "test")
})
}
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项,并启用 metric
// Create a new Orbit feature options and enable metric
opts := orbit.NewOptions().EnableMetric()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
$ curl -i http://127.0.0.1:8080/demo
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE, UPDATE
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type
Access-Control-Max-Age: 172800
Content-Type: text/plain; charset=utf-8
Date: Wed, 10 Jan 2024 12:09:37 GMT
Content-Length: 4
demo
$ curl -i http://127.0.0.1:8080/demo/test
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE, UPDATE
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type
Access-Control-Max-Age: 172800
Content-Type: text/plain; charset=utf-8
Date: Wed, 10 Jan 2024 12:09:43 GMT
Content-Length: 4
test
6. Custom Access Log
To customize the access log format and fields in orbit
, you can use the following example:
Default LogEvent Fields
// LogEvent 结构体用于记录日志事件
// The LogEvent struct is used to log events
type LogEvent struct {
// Message 字段表示日志消息
// The Message field represents the log message
Message string `json:"message,omitempty" yaml:"message,omitempty"`
// ID 字段表示事件的唯一标识符
// The ID field represents the unique identifier of the event
ID string `json:"id,omitempty" yaml:"id,omitempty"`
// IP 字段表示发起请求的IP地址
// The IP field represents the IP address of the request initiator
IP string `json:"ip,omitempty" yaml:"ip,omitempty"`
// EndPoint 字段表示请求的终端点
// The EndPoint field represents the endpoint of the request
EndPoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
// Path 字段表示请求的路径
// The Path field represents the path of the request
Path string `json:"path,omitempty" yaml:"path,omitempty"`
// Method 字段表示请求的HTTP方法
// The Method field represents the HTTP method of the request
Method string `json:"method,omitempty" yaml:"method,omitempty"`
// Code 字段表示响应的HTTP状态码
// The Code field represents the HTTP status code of the response
Code int `json:"statusCode,omitempty" yaml:"statusCode,omitempty"`
// Status 字段表示请求的状态
// The Status field represents the status of the request
Status string `json:"status,omitempty" yaml:"status,omitempty"`
// Latency 字段表示请求的延迟时间
// The Latency field represents the latency of the request
Latency string `json:"latency,omitempty" yaml:"latency,omitempty"`
// Agent 字段表示发起请求的用户代理
// The Agent field represents the user agent of the request initiator
Agent string `json:"agent,omitempty" yaml:"agent,omitempty"`
// ReqContentType 字段表示请求的内容类型
// The ReqContentType field represents the content type of the request
ReqContentType string `json:"reqContentType,omitempty" yaml:"reqContentType,omitempty"`
// ReqQuery 字段表示请求的查询参数
// The ReqQuery field represents the query parameters of the request
ReqQuery string `json:"query,omitempty" yaml:"query,omitempty"`
// ReqBody 字段表示请求的主体内容
// The ReqBody field represents the body of the request
ReqBody string `json:"reqBody,omitempty" yaml:"reqBody,omitempty"`
// Error 字段表示请求中的任何错误
// The Error field represents any errors in the request
Error any `json:"error,omitempty" yaml:"error,omitempty"`
// ErrorStack 字段表示错误的堆栈跟踪
// The ErrorStack field represents the stack trace of the error
ErrorStack string `json:"errorStack,omitempty" yaml:"errorStack,omitempty"`
}
Example
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
"github.com/shengyanli1982/orbit/utils/log"
"go.uber.org/zap"
)
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo" path
g.GET("/demo", func(c *gin.Context) {
// 返回 HTTP 状态码 200 和 "demo" 字符串
// Return HTTP status code 200 and the string "demo"
c.String(http.StatusOK, "demo")
})
}
// customAccessLogger 函数定义了一个自定义的访问日志记录器
// The customAccessLogger function defines a custom access logger
func customAccessLogger(logger *zap.SugaredLogger, event *log.LogEvent) {
// 记录访问日志,包括路径和方法
// Log the access, including the path and method
logger.Infow("access log", "path", event.Path, "method", event.Method)
}
func main() {
// 创建一个新的 Orbit 配置,并设置访问日志事件函数
// Create a new Orbit configuration and set the access log event function
config := orbit.NewConfig().WithAccessLogEventFunc(customAccessLogger)
// 创建一个新的 Orbit 功能选项
// Create a new Orbit feature options
opts := orbit.NewOptions()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
{"level":"INFO","time":"2024-01-10T20:22:01.244+0800","logger":"default","caller":"accesslog/demo.go:24","message":"access log","path":"/demo","method":"GET"}
7. Custom Recovery Log
Http server recovery log allows you to understand what happened when your service encounters a panic. With orbit
, you can customize the recovery log format and fields. Here is an example of how to customize the recovery log format and fields:
Example
package main
import (
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
"github.com/shengyanli1982/orbit/utils/log"
"go.uber.org/zap"
)
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 GET 方法的处理函数,该函数会触发 panic
// Register a GET method handler function on the "/demo" path, this function will trigger a panic
g.GET("/demo", func(c *gin.Context) {
panic("demo")
})
}
// customRecoveryLogger 函数定义了一个自定义的恢复日志记录器
// The customRecoveryLogger function defines a custom recovery logger
func customRecoveryLogger(logger *zap.SugaredLogger, event *log.LogEvent) {
// 记录恢复日志,包括路径、方法、错误和错误堆栈
// Log the recovery, including the path, method, error, and error stack
logger.Infow("recovery log", "path", event.Path, "method", event.Method, "error", event.Error, "errorStack", event.ErrorStack)
}
func main() {
// 创建一个新的 Orbit 配置,并设置恢复日志事件函数
// Create a new Orbit configuration and set the recovery log event function
config := orbit.NewConfig().WithRecoveryLogEventFunc(customRecoveryLogger)
// 创建一个新的 Orbit 功能选项
// Create a new Orbit feature options
opts := orbit.NewOptions()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
{"level":"INFO","time":"2024-01-10T20:27:10.041+0800","logger":"default","caller":"recoverylog/demo.go:22","message":"recovery log","path":"/demo","method":"GET","error":"demo","errorStack":"goroutine 6 [running]:\nruntime/debug.Stack()\n\t/usr/local/go/src/runtime/debug/stack.go:24 +0x65\ngithub.com/shengyanli1982/orbit/internal/middleware.Recovery.func1.1()\n\t/Volumes/DATA/programs/GolandProjects/orbit/internal/middleware/system.go:145 +0x559\npanic({0x170ec80, 0x191cb70})\n\t/usr/local/go/src/runtime/panic.go:884 +0x213\nmain.(*service).RegisterGroup.func1(0x0?)\n\t/Volumes/DATA/programs/GolandProjects/orbit/example/recoverylog/demo.go:17 +0x27\ngithub.com/gin-gonic/gin.(*Context).Next(...)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/context.go:173\ngithub.com/shengyanli1982/orbit/internal/middleware.AccessLogger.func1(0xc0001e6300)\n\t/Volumes/DATA/programs/GolandProjects/orbit/internal/middleware/system.go:59 +0x1a5\ngithub.com/gin-gonic/gin.(*Context).Next(...)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/context.go:173\ngithub.com/shengyanli1982/orbit/internal/middleware.Cors.func1(0xc0001e6300)\n\t/Volumes/DATA/programs/GolandProjects/orbit/internal/middleware/system.go:35 +0x139\ngithub.com/gin-gonic/gin.(*Context).Next(...)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/context.go:173\ngithub.com/shengyanli1982/orbit/internal/middleware.BodyBuffer.func1(0xc0001e6300)\n\t/Volumes/DATA/programs/GolandProjects/orbit/internal/middleware/buffer.go:18 +0x92\ngithub.com/gin-gonic/gin.(*Context).Next(...)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/context.go:173\ngithub.com/shengyanli1982/orbit/internal/middleware.Recovery.func1(0xc0001e6300)\n\t/Volumes/DATA/programs/GolandProjects/orbit/internal/middleware/system.go:166 +0x82\ngithub.com/gin-gonic/gin.(*Context).Next(...)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/context.go:173\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc0000076c0, 0xc0001e6300)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/gin.go:616 +0x66b\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc0000076c0, {0x1924a30?, 0xc0000c02a0}, 0xc0001e6200)\n\t/Volumes/CACHE/programs/gopkgs/pkg/mod/github.com/gin-gonic/gin@v1.8.2/gin.go:572 +0x1dd\nnet/http.serverHandler.ServeHTTP({0xc00008be30?}, {0x1924a30, 0xc0000c02a0}, 0xc0001e6200)\n\t/usr/local/go/src/net/http/server.go:2936 +0x316\nnet/http.(*conn).serve(0xc0000962d0, {0x19253e0, 0xc00008bd40})\n\t/usr/local/go/src/net/http/server.go:1995 +0x612\ncreated by net/http.(*Server).Serve\n\t/usr/local/go/src/net/http/server.go:3089 +0x5ed\n"}
8. Prometheus Metrics
orbit
supports prometheus
metrics. You can enable it using EnableMetric
. Here is an example of how to collect demo
metrics using the demo
service.
[!TIP]
Use curl http://127.0.0.1:8080/metrics to get metrics.
Example
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
)
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo" path
g.GET("/demo", func(c *gin.Context) {
// 返回 HTTP 状态码 200 和 "demo" 字符串
// Return HTTP status code 200 and the string "demo"
c.String(http.StatusOK, "demo")
})
}
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项,并启用 metric
// Create a new Orbit feature options and enable metric
opts := orbit.NewOptions().EnableMetric()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
# HELP orbit_http_request_latency_seconds HTTP request latencies in seconds.
# TYPE orbit_http_request_latency_seconds gauge
orbit_http_request_latency_seconds{method="GET",path="/demo",status="200"} 0
# HELP orbit_http_request_latency_seconds_histogram HTTP request latencies in seconds(Histogram).
# TYPE orbit_http_request_latency_seconds_histogram histogram
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="0.1"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="0.5"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="1"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="2"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="5"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="10"} 3
orbit_http_request_latency_seconds_histogram_bucket{method="GET",path="/demo",status="200",le="+Inf"} 3
orbit_http_request_latency_seconds_histogram_sum{method="GET",path="/demo",status="200"} 0
orbit_http_request_latency_seconds_histogram_count{method="GET",path="/demo",status="200"} 3
9. Repeat Read Request/Response Body
orbit
supports repeating the read request/response body. By default, this behavior is enabled and requires no additional configuration. Here is an example of how to use the demo
service to repeat read the request/response body.
9.1 Repeat Read Request Body
You can use the httptool.GenerateRequestBody
method to obtain the request body bytes and cache them. This allows you to read the cached bytes when needed.
[!IMPORTANT]
The request body is an io.ReadCloser
, which is a stream that can only be read once. If you need to read it again, do not read it directly. Instead, use orbit
to cache it.
Example
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
"github.com/shengyanli1982/orbit/utils/httptool"
)
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 POST 方法的处理函数
// Register a POST method handler function on the "/demo" path
g.POST("/demo", func(c *gin.Context) {
// 重复读取请求体内容 20 次
// Repeat the read request body content 20 times
for i := 0; i < 20; i++ {
// 生成请求体
// Generate the request body
if body, err := httptool.GenerateRequestBody(c); err != nil {
// 如果生成请求体出错,返回 HTTP 状态码 500 和错误信息
// If there is an error generating the request body, return HTTP status code 500 and the error message
c.String(http.StatusInternalServerError, err.Error())
} else {
// 如果生成请求体成功,返回 HTTP 状态码 200 和请求体内容
// If the request body is successfully generated, return HTTP status code 200 and the request body content
c.String(http.StatusOK, fmt.Sprintf(">> %d, %s\n", i, string(body)))
}
}
})
}
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项
// Create a new Orbit feature options
opts := orbit.NewOptions()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 模拟一个请求
// Simulate a request
resp, _ := http.Post("http://localhost:8080/demo", "text/plain", io.Reader(bytes.NewBuffer([]byte("demo"))))
defer resp.Body.Close()
// 打印响应体
// Print the response body
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(resp.Body)
fmt.Println(buf.String())
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
$ go run demo.go
[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 --> github.com/shengyanli1982/orbit.healthcheckService.func1 (1 handlers)
[GIN-debug] POST /demo --> main.(*service).RegisterGroup.func1 (5 handlers)
{"level":"INFO","time":"2024-01-13T10:27:37.531+0800","logger":"default","caller":"orbit/gin.go:165","message":"http server is ready","address":"127.0.0.1:8080"}
{"level":"INFO","time":"2024-01-13T10:27:37.534+0800","logger":"default","caller":"log/default.go:10","message":"http server access log","id":"","ip":"127.0.0.1","endpoint":"127.0.0.1:58618","path":"/demo","method":"POST","code":200,"status":"OK","latency":"50.508µs","agent":"Go-http-client/1.1","query":"","reqContentType":"text/plain","reqBody":""}
>> 0, demo
>> 1, demo
>> 2, demo
>> 3, demo
>> 4, demo
>> 5, demo
>> 6, demo
>> 7, demo
>> 8, demo
>> 9, demo
>> 10, demo
>> 11, demo
>> 12, demo
>> 13, demo
>> 14, demo
>> 15, demo
>> 16, demo
>> 17, demo
>> 18, demo
>> 19, demo
{"level":"INFO","time":"2024-01-13T10:28:07.537+0800","logger":"default","caller":"orbit/gin.go:190","message":"http server is shutdown","address":"127.0.0.1:8080"}
9.2 Repeat Read Response Body
The httptool.GenerateResponseBody
method can be used to retrieve the response body bytes from the cache. It is important to note that you should call httptool.GenerateResponseBody
after writing the actual response body, such as using c.String(http.StatusOK, "demo")
.
[!NOTE]
The response body is always written to an io.Writer
, so direct reading is not possible. If you need to read it, you can use orbit
to cache it.
httptool.GenerateResponseBody
is often used in custom middleware to retrieve the response body bytes and perform additional actions.
Example
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/shengyanli1982/orbit"
"github.com/shengyanli1982/orbit/utils/httptool"
)
// customMiddleware 函数定义了一个自定义的中间件
// The customMiddleware function defines a custom middleware
func customMiddleware() gin.HandlerFunc {
// 返回一个 Gin 的 HandlerFunc
// Return a Gin HandlerFunc
return func(c *gin.Context) {
// 调用下一个中间件或处理函数
// Call the next middleware or handler function
c.Next()
// 从上下文中获取响应体缓冲区
// Get the response body buffer from the context
for i := 0; i < 20; i++ {
// 生成响应体
// Generate the response body
body, _ := httptool.GenerateResponseBody(c)
// 打印响应体
// Print the response body
fmt.Printf("# %d, %s\n", i, string(body))
}
}
}
// 定义 service 结构体
// Define the service struct
type service struct{}
// RegisterGroup 方法将路由组注册到 service
// The RegisterGroup method registers a router group to the service
func (s *service) RegisterGroup(g *gin.RouterGroup) {
// 在 "/demo" 路径上注册一个 GET 方法的处理函数
// Register a GET method handler function on the "/demo" path
g.GET("/demo", func(c *gin.Context) {
// 返回 HTTP 状态码 200 和 "demo" 字符串
// Return HTTP status code 200 and the string "demo"
c.String(http.StatusOK, "demo")
})
}
func main() {
// 创建一个新的 Orbit 配置
// Create a new Orbit configuration
config := orbit.NewConfig()
// 创建一个新的 Orbit 功能选项,并启用 metric
// Create a new Orbit feature options and enable metric
opts := orbit.NewOptions().EnableMetric()
// 创建一个新的 Orbit 引擎
// Create a new Orbit engine
engine := orbit.NewEngine(config, opts)
// 注册一个自定义的中间件
// Register a custom middleware
engine.RegisterMiddleware(customMiddleware())
// 注册一个自定义的路由组
// Register a custom router group
engine.RegisterService(&service{})
// 启动引擎
// Start the engine
engine.Run()
// 模拟一个请求
// Simulate a request
resp, _ := http.Get("http://localhost:8080/demo")
defer resp.Body.Close()
// 等待 30 秒
// Wait for 30 seconds
time.Sleep(30 * time.Second)
// 停止引擎
// Stop the engine
engine.Stop()
}
Result
$ go run demo.go
[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 --> github.com/shengyanli1982/orbit.healthcheckService.func1 (1 handlers)
[GIN-debug] GET /metrics --> github.com/shengyanli1982/orbit/utils/wrapper.WrapHandlerToGin.func1 (2 handlers)
[GIN-debug] GET /demo --> main.(*service).RegisterGroup.func1 (7 handlers)
{"level":"INFO","time":"2024-01-13T10:32:25.191+0800","logger":"default","caller":"orbit/gin.go:165","message":"http server is ready","address":"127.0.0.1:8080"}
{"level":"INFO","time":"2024-01-13T10:32:25.194+0800","logger":"default","caller":"log/default.go:10","message":"http server access log","id":"","ip":"127.0.0.1","endpoint":"127.0.0.1:59139","path":"/demo","method":"GET","code":200,"status":"OK","latency":"20.326µs","agent":"Go-http-client/1.1","query":"","reqContentType":"","reqBody":""}
# 0, demo
# 1, demo
# 2, demo
# 3, demo
# 4, demo
# 5, demo
# 6, demo
# 7, demo
# 8, demo
# 9, demo
# 10, demo
# 11, demo
# 12, demo
# 13, demo
# 14, demo
# 15, demo
# 16, demo
# 17, demo
# 18, demo
# 19, demo
{"level":"INFO","time":"2024-01-13T10:32:55.195+0800","logger":"default","caller":"orbit/gin.go:190","message":"http server is shutdown","address":"127.0.0.1:8080"}