手把手带你使用 go-kit(客户端直连)
我们客户端的架构与服务的类似
根据我们Demo的示例,我们创建一个客户端,客户端与服务端架构类似
// 项目结构
-| Client
----| Client.go
-| EndPoint
----| endpoint.go
-| Transport
----| Transport.go
- main.go
1.首先我们还是先写Client实例
// Client/client.go
package Client
import (
"context"
"fmt"
"github.com/go-kit/kit/transport/http"
"net/url"
"strings"
)
// Direct: 直接调用服务端
// method:方法 fullUrl: 完整的url http://localhost:8000
// enc: http.EncodeRequestFunc dec: http.DecodeResponseFunc 这两个函数具体等一下会在Transport中进行详细解释
// requestStruct: 根据EndPoint定义的request结构体传参
func Direct(method string, fullUrl string, enc http.EncodeRequestFunc, dec http.DecodeResponseFunc, requestStruct interface{}) (interface{}, error) {
// 1.解析url
target, err := url.Parse(fullUrl)
if err != nil {
fmt.Println(err)
return nil, err
}
// kit调用服务端拿到Client对象
client := http.NewClient(strings.ToUpper(method), target, enc, dec)
// 调用服务 client.Endpoint()返回一个可执行函数 传入context 和 请求数据结构体
return client.Endpoint()(context.Background(), requestStruct)
}
2.EndPoint与之前没有变化,删除了一些逻辑
// EndPoint/endpoint.go
package EndPoint
// endpoint.go 定义 Request、Response 格式, 并且可以使用闭包来实现各种中间件的嵌套
// 这里了解 protobuf 的比较好理解点
// 就是声明 接收数据和响应数据的结构体 并通过构造函数创建 在创建的过程当然可以使用闭包来进行一些你想要的操作啦
// 这里根据我们Demo来创建一个响应和请求
// 当然你想怎么创建怎么创建 也可以共用 这里我分开写 便于大家看的清楚
// Hello 业务使用的请求和响应格式
// HelloRequest 请求格式
type HelloRequest struct {
Name string `json:"name"`
}
// HelloResponse 响应格式
type HelloResponse struct {
Reply string `json:"reply"`
}
// Bye 业务使用的请求和响应格式
// ByeRequest 请求格式
type ByeRequest struct {
Name string `json:"name"`
}
// ByeResponse 响应格式
type ByeResponse struct {
Reply string `json:"reply"`
}
// ------------ 当然 也可以通用的写 ----------
// Request 请求格式
type Request struct {
Name string `json:"name"`
}
// Response 响应格式
type Response struct {
Reply string `json:"reply"`
}
3.修改Transport内容,逻辑与服务的正好相反
// Transport/transport.go
package Transport
import (
"Songzhibin/go-kit-demo/v0client/EndPoint"
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"strconv"
)
// Transport/transport.go 主要负责HTTP、gRpc、thrift等相关的逻辑
// 这里有两个关键函数
// DecodeRequest & EncodeResponse 函数签名是固定的哟
// func EncodeRequestFunc (context.Context, *http.Request, interface{}) error
// func DecodeResponseFunc (context.Context, *http.Response) (response interface{}, err error)
// HelloEncodeRequestFunc: 处理请求数据符合服务方要求的数据
func HelloEncodeRequestFunc(c context.Context, request *http.Request, r interface{}) error {
// r就是我们在EndPoint中定义的请求响应对象
req, ok := r.(EndPoint.HelloRequest)
if !ok {
return errors.New("断言失败")
}
// 拿到自定义的请求对象对url做业务处理
request.URL.Path += "/hello"
data := url.Values{}
data.Set("name", req.Name)
request.URL.RawQuery = data.Encode()
// 实际上这里做的就是增加url参数 body之类的一些事情,简而言之就是构建http请求需要的一些资源
return nil
}
// HelloDecodeResponseFunc: 解密服务方传回的数据
func HelloDecodeResponseFunc(c context.Context, res *http.Response) (response interface{}, err error) {
// 判断响应
if res.StatusCode != 200 {
return nil, errors.New("异常的响应码" + strconv.Itoa(res.StatusCode))
}
// body中的内容需要我们解析成我们通用定义好的内容
var r EndPoint.HelloResponse
err = json.NewDecoder(res.Body).Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}
// ByeEncodeRequestFunc: 处理请求数据符合服务方要求的数据
func ByeEncodeRequestFunc(c context.Context, request *http.Request, r interface{}) error {
// r就是我们在EndPoint中定义的请求响应对象
req, ok := r.(EndPoint.HelloRequest)
if !ok {
return errors.New("断言失败")
}
// 拿到自定义的请求对象对url做业务处理
request.URL.Path += "/bye"
data := url.Values{}
data.Set("name", req.Name)
request.URL.RawQuery = data.Encode()
// 实际上这里做的就是增加url参数 body之类的一些事情,简而言之就是构建http请求需要的一些资源
return nil
}
// ByeDecodeResponseFunc: 解密服务方传回的数据
func ByeDecodeResponseFunc(c context.Context, res *http.Response) (response interface{}, err error) {
// 判断响应
if res.StatusCode != 200 {
return nil, errors.New("异常的响应码" + strconv.Itoa(res.StatusCode))
}
// body中的内容需要我们解析成我们通用定义好的内容
var r EndPoint.HelloResponse
err = json.NewDecoder(res.Body).Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}
直接调用
// main.go
package main
import (
"Songzhibin/go-kit-demo/v0client/Client"
"Songzhibin/go-kit-demo/v0client/EndPoint"
"Songzhibin/go-kit-demo/v0client/Transport"
"fmt"
)
// 调用我们在client封装的函数就好了
func main() {
i, err := Client.Direct("GET", "http://127.0.0.1:8000", Transport.HelloEncodeRequestFunc, Transport.HelloDecodeResponseFunc, EndPoint.HelloRequest{Name: "songzhibin"})
if err != nil {
fmt.Println(err)
return
}
res, ok := i.(EndPoint.HelloResponse)
if !ok {
fmt.Println("no ok")
return
}
fmt.Println(res)
}
Songzhibin