概念

kratos 为了使http协议的逻辑代码和grpc的逻辑代码使用同一份,选择了基于protobuf的IDL文件使用proto插件生成辅助代码的方式。

protoc http插件的地址为:https://github.com/go-kratos/kratos/tree/main/cmd/protoc-gen-go-http

示例

syntax = "proto3";

package helloworld;

option go_package = "test/helloworld;helloworld";
option java_multiple_files = true;
option java_package = "helloworld";
import "google/api/annotations.proto"; service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/helloworld", // 声明路由
body: "*"
};
}
} message HelloRequest {
string name = 1;
} message HelloReply {
string msg = 1;
}

使用kratos proto client xxx 生成的代码为:

// Code generated by protoc-gen-go-http. DO NOT EDIT.
// versions:
// - protoc-gen-go-http v2.4.0
// - protoc v3.19.4
// source: helloworld/helloworld.proto package helloworld import (
context "context"
http "github.com/go-kratos/kratos/v2/transport/http"
binding "github.com/go-kratos/kratos/v2/transport/http/binding"
) // This is a compile-time assertion to ensure that this generated file
// is compatible with the kratos package it is being compiled against.
var _ = new(context.Context)
var _ = binding.EncodeURL const _ = http.SupportPackageIsVersion1 const OperationGreeterSayHello = "/helloworld.Greeter/SayHello" type GreeterHTTPServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
} func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
r := s.Route("/")
r.POST("/helloworld", _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
if err := ctx.Bind(&in); err != nil {
return err
}
http.SetOperation(ctx, OperationGreeterSayHello)
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, req.(*HelloRequest))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*HelloReply)
return ctx.Result(200, reply)
}
} type GreeterHTTPClient interface {
SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error)
} type GreeterHTTPClientImpl struct {
cc *http.Client
} func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient {
return &GreeterHTTPClientImpl{client}
} func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) {
var out HelloReply
pattern := "/helloworld"
path := binding.EncodeURL(pattern, in, false)
opts = append(opts, http.Operation(OperationGreeterSayHello))
opts = append(opts, http.PathTemplate(pattern))
err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...)
if err != nil {
return nil, err
}
return &out, err
}

开启一个grpc及http服务:

package main

import (
"context"
"fmt"
"log"
"test/helloworld" "github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
) type server struct {
helloworld.UnimplementedGreeterServer
} func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
return &helloworld.HelloReply{Msg: fmt.Sprintf("Hello %+v", in.Name)}, nil
} func main() {
s := &server{}
httpSrv := http.NewServer(
http.Address(":8000"),
http.Middleware(
recovery.Recovery(),
),
)
grpcSrv := grpc.NewServer(
grpc.Address(":9000"),
grpc.Middleware(
recovery.Recovery(),
),
) helloworld.RegisterGreeterServer(grpcSrv, s)
helloworld.RegisterGreeterHTTPServer(httpSrv, s) app := kratos.New(
kratos.Name("test"),
kratos.Server(
httpSrv,
grpcSrv,
),
) if err := app.Run(); err != nil {
log.Fatal(err)
}
}

http client:

package main

import (
"context"
"log"
"test/helloworld" "github.com/go-kratos/kratos/v2/middleware/recovery"
transhttp "github.com/go-kratos/kratos/v2/transport/http"
) func main() {
callHTTP()
} func callHTTP() {
conn, err := transhttp.NewClient(
context.Background(),
transhttp.WithMiddleware(
recovery.Recovery(),
),
transhttp.WithEndpoint("127.0.0.1:8000"),
)
if err != nil {
panic(err)
}
defer conn.Close()
client := helloworld.NewGreeterHTTPClient(conn)
reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
if err != nil {
log.Fatal(err)
}
log.Printf("[http] SayHello %s\n", reply.Msg)
}

http server端实现原理

核心流程为下图 :

首先新建一个struct 并实现 http_pb.go种 GreeterHTTPServer interface  的方法,GreeterHTTPServer的命名方式为protobuf文件中的 service+HTTPServer,interface的方法为protobuf中使用google.api.http生命http路由所有的method。

然后使用RegisterGreeterHTTPServer方法把服务注册进去。大体的流程如下:

const OperationGreeterSayHello = "/helloworld.Greeter/SayHello"

func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
r := s.Route("/")
r.POST("/helloworld", _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 // protobuf 中声明的request
if err := ctx.Bind(&in); err != nil { // 把http的参数绑定到 in
return err
}
http.SetOperation(ctx, OperationGreeterSayHello) // 设置Operation 和grpc一值,用于middleware select 等
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, req.(*HelloRequest)) // 这个方法也就是上文提到的GreeterHTTPServer接口的方法,也就是我们自己实现的struct server里的SayHello方法
}) // 使用责任链模式middleware 这里没有任何中间件
out, err := h(ctx, &in) // 执行
if err != nil {
return err
}
reply := out.(*HelloReply)
return ctx.Result(200, reply)
}
}

什么事责任链模式?

https://haiyux.cc/post/designmode/behavioral/#责任链模式

上段代码中的POST方法为:

代码在https://github.com/go-kratos/kratos/blob/main/transport/http/router.go#L76

func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodPost, path, h, m...) // MethodPost = POST net/http下的常量
} // h 为上段xxx_http_pb.go代码中_Greeter_SayHello0_HTTP_Handler的返回值
func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {
next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
ctx := r.pool.Get().(Context)
ctx.Reset(res, req) // 把 net/http的http.ResponseWriter 和*http.Request 设置ctx中
if err := h(ctx); err != nil { // 执行h
r.srv.ene(res, req, err) // 如果出错了 执行 ene(EncodeErrorFunc)
}
ctx.Reset(nil, nil)
r.pool.Put(ctx)
}))
next = FilterChain(filters...)(next)
next = FilterChain(r.filters...)(next) // 添加filter 责任链模式
r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method) // router 为 mux的router 把方法注册到路由中
}

当我们访问 path.Join(r.prefix, relativePath)也就是/helloworld 时,会执行上段代码中的next方法,next是一个责任链。

核心为会执行_Greeter_SayHello0_HTTP_Handler方法,

如果没发生错误,执行ctx.Result(200, reply)

type wrapper struct {
router *Router
req *http.Request
res http.ResponseWriter
w responseWriter
} func (c *wrapper) Result(code int, v interface{}) error {
c.w.WriteHeader(code)
return c.router.srv.enc(&c.w, c.req, v)
}

enc也就是EncodeResponseFunc, 为kratos预留的返回值函数

type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error

kratos提供了默认的EncodeResponseFunc

func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
if v == nil {
return nil
}
if rd, ok := v.(Redirector); ok { // 检查有无Redirect方法,如果实现了interface 为跳转路由 也就是http的301 302等
url, code := rd.Redirect()
http.Redirect(w, r, url, code) // 跳转
return nil
}
codec, _ := CodecForRequest(r, "Accept") // 查看需要返回的参数类型 比如json
data, err := codec.Marshal(v) // 把数据Marshal成[]byte
if err != nil {
return err
}
w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) // 设置header
_, err = w.Write(data) // 写数据
if err != nil {
return err
}
return nil
}

如果没发生错误,执行ene,也就是EncodeErrorFunc, 为kratos预留的错误返回值删除

type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)

kratos提供了默认的EncodeErrorFunc

func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
se := errors.FromError(err) // 把error变成自定义的实现error的结构体
codec, _ := CodecForRequest(r, "Accept") // 查看需要返回的参数类型 比如json
body, err := codec.Marshal(se)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
w.WriteHeader(int(se.Code)) // 写入 error中的code
_, _ = w.Write(body) // 返回错误信息
}

http client端实现原理

在上传的代码中http client的部分为

type GreeterHTTPClient interface {
SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error)
} type GreeterHTTPClientImpl struct { // 实现 GreeterHTTPClient 接口
cc *http.Client
} func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient {
return &GreeterHTTPClientImpl{client}
} func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) {
var out HelloReply // 返回值
pattern := "/helloworld"
path := binding.EncodeURL(pattern, in, false) // 整理path 传入in 是由于可能有path参数或者query
opts = append(opts, http.Operation(OperationGreeterSayHello))
opts = append(opts, http.PathTemplate(pattern))
err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) // 访问接口
if err != nil {
return nil, err
}
return &out, err
}

上段代码中的Invoke方法为:

代码在https://github.com/go-kratos/kratos/blob/main/transport/http/client.go#L192

func (client *Client) Invoke(ctx context.Context, method, path string, args interface{}, reply interface{}, opts ...CallOption) error {
var (
contentType string
body io.Reader
)
c := defaultCallInfo(path)
for _, o := range opts {
if err := o.before(&c); err != nil {
return err
}
}
if args != nil {
data, err := client.opts.encoder(ctx, c.contentType, args)
if err != nil {
return err
}
contentType = c.contentType
body = bytes.NewReader(data)
}
url := fmt.Sprintf("%s://%s%s", client.target.Scheme, client.target.Authority, path)
req, err := http.NewRequest(method, url, body)
if err != nil {
return err
}
if contentType != "" {
req.Header.Set("Content-Type", c.contentType)
}
if client.opts.userAgent != "" {
req.Header.Set("User-Agent", client.opts.userAgent)
}
ctx = transport.NewClientContext(ctx, &Transport{
endpoint: client.opts.endpoint,
reqHeader: headerCarrier(req.Header),
operation: c.operation,
request: req,
pathTemplate: c.pathTemplate,
})
return client.invoke(ctx, req, args, reply, c, opts...)
} func (client *Client) invoke(ctx context.Context, req *http.Request, args interface{}, reply interface{}, c callInfo, opts ...CallOption) error {
h := func(ctx context.Context, in interface{}) (interface{}, error) {
res, err := client.do(req.WithContext(ctx))
if res != nil {
cs := csAttempt{res: res}
for _, o := range opts {
o.after(&c, &cs)
}
}
if err != nil {
return nil, err
}
defer res.Body.Close()
if err := client.opts.decoder(ctx, res, reply); err != nil {
return nil, err
}
return reply, nil
}
var p selector.Peer
ctx = selector.NewPeerContext(ctx, &p)
if len(client.opts.middleware) > 0 {
h = middleware.Chain(client.opts.middleware...)(h)
}
_, err := h(ctx, args)
return err
}

kratos http原理的更多相关文章

  1. 通过 layout 探索 kratos 运行原理

    创建项目 首先需要安装好对应的依赖环境,以及工具: go 下载 protoc go install google.golang.org/protobuf/cmd/protoc-gen-go@lates ...

  2. kratos

    技术文章 日志库的使用姿势 通过 layout 探索 kratos 运行原理 发版日志 发布日志 - kratos v2.0.5 版本发布 发布日志 - kratos v2.0.4 版本发布

  3. go微服务框架kratos学习笔记八 (kratos的依赖注入)

    目录 go微服务框架kratos学习笔记八(kratos的依赖注入) 什么是依赖注入 google wire kratos中的wire Providers injector(注入器) Binding ...

  4. 从kratos分析BBR限流源码实现

    什么是自适应限流 自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load.CPU 使用率.总体平均 RT.入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流 ...

  5. 奇异值分解(SVD)原理与在降维中的应用

    奇异值分解(Singular Value Decomposition,以下简称SVD)是在机器学习领域广泛应用的算法,它不光可以用于降维算法中的特征分解,还可以用于推荐系统,以及自然语言处理等领域.是 ...

  6. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  7. 线性判别分析LDA原理总结

    在主成分分析(PCA)原理总结中,我们对降维算法PCA做了总结.这里我们就对另外一种经典的降维方法线性判别分析(Linear Discriminant Analysis, 以下简称LDA)做一个总结. ...

  8. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  9. H5单页面手势滑屏切换原理

    H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...

  10. .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

    .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...

随机推荐

  1. pdfz Vue-3-Cheat-Sheet-zh 官方要是能下载下来,我也就不放这里了。 VUE3 composition API cheat sheet

    https://files.cnblogs.com/files/pengchenggang/Vue-3-Cheat-Sheet-zh.zip 官方下载地址:https://www.vuemastery ...

  2. 基于4G的智能工牌解决方案特色解析

    前记  随着数字化的不断发展以及cat1模块的竞争加剧.cat1无论从成本或者功耗上,都进化的特别快.这样的前提下,让基于4G可穿戴产品逐渐成为现实可穿戴产品必备.能解决以前很多不能解决的问题.  作 ...

  3. AWS API Gateway IP WhileList

    首先创建个API,然后进入API配置,点击左边的资源配置,加入以下配置: { "Version": "2012-10-17", "Statement& ...

  4. 22_播放器之使用SDL显示YUV视频

    简介 使用SDL实现简单的YUV播放器. 这里还需要使用到像素格式和计算图片大小,这两个我们直接使用ffmpeg来实现,因此需要使用ffmpeg的libavutil/avutil.h和libavuti ...

  5. 【atcoder abc281_d】动态规划

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @ ...

  6. 记一次 .NET某施工建模软件 卡死分析

    一:背景 1. 讲故事 前几天有位朋友在微信上找到我,说他的软件卡死了,分析了下也不知道是咋回事,让我帮忙看一下,很多朋友都知道,我分析dump是免费的,当然也不是所有的dump我都能搞定,也只能尽自 ...

  7. openApi generator总是生成类名为 defaultApi

    生成器可以开启 useTags 设置,开启之后会根据 api 文档中的 tags 生成前缀类名,因此,要不生成 defaultApi 需要以下操作: 1.openApi 文档中每个 url 必须要有 ...

  8. 记录--工程化第一步这个package.json要真的搞明白才行

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 工程化最开始就是package.json开始的,很多人学了很多年也没搞清楚这个为什么这么神奇,其实有些字段是在特定场景才有效的,那每个属性 ...

  9. Redis无法向磁盘写入RBD数据

    2020-12-09 11:52:25|21965|ERROR|storage/DRedisAsyncCallback.cpp:394[cbIncrby]Cmd 'INCRBY' failed, ke ...

  10. 散列表的数据结构以及对象在JVM堆中的存储过程

    [版权声明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/129916399 出自[进步* ...