概念

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. openssl 版本兼容问题 备忘录

    第三方依赖openssl,但openssl却有版本不同符号不兼容的问题,由于条件限制不得不使用固定版本的openssl,又或者同时有两个第三方依赖不同版本的openssl,只能靠手动,为了备忘. 1. ...

  2. do{}while(0)用法

    更安全的展开宏定义 #define DOSOMETHING foo1(); foo2(); if(condition) DOSOMETHING /* if(condition) foo1(); foo ...

  3. python基础十(常用模块)

    一 time与datetime模块 1.time import time # 时间分为三种格式: # 1.时间戳:从1970年到现在经过的秒数 # 作用:用于时间间隔的计算 print(time.ti ...

  4. 记录--JavaScript 中有趣的 9 个常用编码套路

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1️⃣ set对象:数组快速去重 常规情况下,我们想要筛选唯一值,一般会想到遍历数组然后逐个对比,或者使用成熟的库比如lodash之类的. ...

  5. 深入浅出Java 23种设计模式,最全PDF版本终于开放下载了!!(文末有福利)

    写在前面 在「 冰河技术 」微信公众号中[设计模式专题]更新完毕已有一段时间了.不少小伙伴在我微信上留言说:冰河,你能不能把[设计模式专题]的文章汇总成PDF文档呢?一直没有时间整理,最近在公众号后台 ...

  6. Oracle 隐式数据类型转换

    Oracle类型转换规则: 对于insert和update操作,oracle将值转换为受影响的的列的类型. 对于select操作,oracle会将列的值的类型转换为目标变量的类型. 看如下实验: 1. ...

  7. java基础 韩顺平老师的 面向对象(高级) 自己记的部分笔记

    373,类变量引出 代码就提到了问题分析里的3点 package com.hspedu.static_; public class ChildGame { public static void mai ...

  8. Python爬虫爬取ECVA论文标题、作者、链接

    1 import re 2 import requests 3 from bs4 import BeautifulSoup 4 import lxml 5 import traceback 6 imp ...

  9. WPF实现树形表格控件(TreeListView)

    前言 本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项.我们将介绍如何使用WPF提供的控件.模板.布局.数据绑定等技术来构建这样一个 ...

  10. #树形dp#nssl 1469 W

    分析 首先一些结论,每条边最多被翻一次,而且由翻的边所构成的连通块答案就是度数为奇数的点的个数的一半, 因为在连通块内必然选择两个叶子节点间的路径翻是最优的,所以也就是选择两个度数为奇数的点,所以结论 ...