kratos的http server主要目的只有一个:
把收到的http请求让pb文件定义的grpc service执行,然后把对应的返回转化成http响应返回.
一旦明确后,思路就很清晰了:
- 需要有http server来响应http请求
- 把pb文件标注的rpc service转化成http路由,http请求转化为grpc request,grpc返回转化为http响应
- 把2转化的路由注册到1生成的http server
kratos http server
首先来看看对应的结构
// Server is an HTTP server wrapper.
type Server struct {
*http.Server //kratos内嵌了一个golang内置的http server
lis net.Listener
tlsConf *tls.Config
endpoint *url.URL
err error
network string
address string
timeout time.Duration
filters []FilterFunc // 过滤器
middleware matcher.Matcher // 中间件
dec DecodeRequestFunc // 请求解码
enc EncodeResponseFunc // 返回编码
ene EncodeErrorFunc// 错误返回编码
strictSlash bool
router *mux.Router //处理http路由,则采用了mux.Router来处理
}
// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {
srv := &Server{
network: "tcp",
address: ":0",
timeout: 1 * time.Second,
middleware: matcher.New(),
dec: DefaultRequestDecoder,
enc: DefaultResponseEncoder,
ene: DefaultErrorEncoder,
strictSlash: true,
}
for _, o := range opts {
o(srv)
}
srv.router = mux.NewRouter().StrictSlash(srv.strictSlash)
srv.router.NotFoundHandler = http.DefaultServeMux
srv.router.MethodNotAllowedHandler = http.DefaultServeMux
srv.router.Use(srv.filter())
// 下面是关键,实现了我们目的的第一步:
// 有http server来响应对应的请求.同时采用了mux的Router来作为http server的handler
// 那么接下里要做的,就是得想案发
srv.Server = &http.Server{
Handler: FilterChain(srv.filters...)(srv.router),
TLSConfig: srv.tlsConf,
}
return srv
}
func (s *Server) filter() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var (
ctx context.Context
cancel context.CancelFunc
)
if s.timeout > 0 {
ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
} else {
ctx, cancel = context.WithCancel(req.Context())
}
defer cancel()
pathTemplate := req.URL.Path
if route := mux.CurrentRoute(req); route != nil {
// /path/123 -> /path/{id}
pathTemplate, _ = route.GetPathTemplate()
}
// 和kratos grpc一样,把这个过滤器作为请求首个过滤器,把对应的服务器等相关信息放入Transport里面
// 然后再把Transport放到Context里面传递给执行函数
tr := &Transport{
operation: pathTemplate,
pathTemplate: pathTemplate,
reqHeader: headerCarrier(req.Header),
replyHeader: headerCarrier(w.Header()),
request: req,
}
if s.endpoint != nil {
tr.endpoint = s.endpoint.String()
}
tr.request = req.WithContext(transport.NewServerContext(ctx, tr))
next.ServeHTTP(w, tr.request)
})
}
}
上面已经实现了第一步了,那么我们得研究一下,怎么样把对应的http 请求转化成路由呢?
我们得看看kratos的两个关键性设计:Router和Context:
首先是Context
// Context is an HTTP Context.
// 我们可以看出,这个Context接口除了包括了原来的golang Context接口的外,还提供了:返回http相关信息,绑定对应http信息等方法
// 简单来说,这个Context主要作用是转化层作用,就像前面说的:把pb文件标注的rpc service转化成http路由
// http请求转化为grpc request,grpc返回转化为http响应
type Context interface {
context.Context
Vars() url.Values
Query() url.Values
Form() url.Values
Header() http.Header
Request() *http.Request
Response() http.ResponseWriter
Middleware(middleware.Handler) middleware.Handler
Bind(interface{}) error
BindVars(interface{}) error
BindQuery(interface{}) error
BindForm(interface{}) error
Returns(interface{}, error) error
Result(int, interface{}) error
JSON(int, interface{}) error
XML(int, interface{}) error
String(int, string) error
Blob(int, string, []byte) error
Stream(int, string, io.Reader) error
Reset(http.ResponseWriter, *http.Request)
}
// 具体实现接口的struct
type wrapper struct {
// 绑定的路由
router *Router
// 对应请求的http请求和相应
req *http.Request
res http.ResponseWriter
// 输出响应
w responseWriter
}
type responseWriter struct {
code int
w http.ResponseWriter
}
然后是Router
// Router is an HTTP router.
//
type Router struct {
prefix string
pool sync.Pool
srv *Server
filters []FilterFunc
}
func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router {
r := &Router{
prefix: prefix,
srv: srv,
filters: filters,
}
r.pool.New = func() interface{} {
return &wrapper{router: r}
}
return r
}
// HandlerFunc defines a function to serve HTTP requests.
type HandlerFunc func(Context) error
// Handle registers a new route with a matcher for the URL path and method.
// Handle是关键方法,主要作用是把对应的HandlerFunc注册到http server的mux.Router里面
// 同时可以看到他注册的的路由函数的形式HandlerFunc是接受一个HandlerFunc类型的函数,传入的是前面说的从Context类型,返回的是error
func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {
next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
// 值得注意的是,这里用了sync.Pool来减少内存分配
ctx := r.pool.Get().(Context)
ctx.Reset(res, req)
if err := h(ctx); err != nil {
// 如果有错误的话,会在此处执行那个响应
r.srv.ene(res, req, err)
}
ctx.Reset(nil, nil)
r.pool.Put(ctx)
}))
next = FilterChain(filters...)(next)
next = FilterChain(r.filters...)(next)
r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method)
}
看完上面的介绍,基本可以确定思路了
- Router是负责注册路由到http server的mux.Router里面的.同时,接受的注册函数是传参为Context,返回为error
- Context含有多个用途:包括解码http请求后解析为grpc请求,编码grpc返回为http响应的.
具体的生成http代码解析
// 利用server生产新的路由
func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
r := s.Route("/")
r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
}
func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in HelloRequest
// 解析路由参数和请求参数到grpc请求
if err := ctx.BindQuery(&in); err != nil {
return err
}
if err := ctx.BindVars(&in); err != nil {
return err
}
http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
// 后面会着重讲一下Middleware的实现
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, req.(*HelloRequest))
})
// 值得注意的是,如果是执行返回err,就直接返回
// 如果成功没有err的话,这边就直接进行http响应
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*HelloReply)
return ctx.Result(200, reply)
}
}
// 可以看到,这边调用的中间件和kratos的grpc模块一样,都使用了同样的Matcher
// 通过这个使得kratos写的中间件可以为http和grpc模块共用
func (c *wrapper) Middleware(h middleware.Handler) middleware.Handler {
if tr, ok := transport.FromServerContext(c.req.Context()); ok {
return middleware.Chain(c.router.srv.middleware.Match(tr.Operation())...)(h)
}
return middleware.Chain(c.router.srv.middleware.Match(c.req.URL.Path)...)(h)
}
-
通过上面可以看出,生成的代码中,主要是要把形式func(ctx http.Context) error的函数注册到Router
-
这个函数除了简单的解析请求外,还会把中间件给串联起来
-
值得注意的是,如果返回没有error,会在这个函数里面就响应请求了,否则,则直接返回,交由Router来处理.
if err := h(ctx); err != nil { // 如果有错误的话,会在此处执行那个响应 r.srv.ene(res, req, err) }
而如果没有err返回的响应代码是:
func (c *wrapper) Result(code int, v interface{}) error { c.w.WriteHeader(code) return c.router.srv.enc(&c.w, c.req, v) }
个人认为实际上可以统一交由Router来决定响应会好点