Gateway Api库原理

Gateway Api

1、兼容Micro Api

  • 支持OPTIONS请求,处理CORS
  • 优先处理自定义路由
  • 处理固定路由,解析ServiceName、MethodName

2、固定路由 拿到MethodName后 怎么知道是否存在?怎么初始化参数?怎么调用方法呢?

  • 根据 xxx.pb.micro.go 生成:xxxx.pb.xinyan.go,返回 ServiceName、MethodMap
  func (o *i) GetServiceName() string {
    return "Friends"
  }

  func (o *i) GetMethodMap() map[string]func() (proto.Message, proto.Message) {
    return map[string]func() (proto.Message, proto.Message){
      "GetFriends": func() (req proto.Message, rsp proto.Message) {
        req = &empty.Empty{}
        rsp = &common.Friends{}
        return
      },
      "DeleteFriend": func() (req proto.Message, rsp proto.Message) {
        req = &DeleteFriendReq{}
        rsp = &empty.Empty{}
        return
      },
    }
  }
  • 根据content-type来初始化 参数、返回值

    contentType := r.Header.Get("content-type")
    if strings.Contains(contentType, "application/json") {
    err = json.Unmarshal(body, req)
    } else {
    err = proto.Unmarshal(body, req)
    }
    
  • 反射调用函数:

      args := []reflect.Value{
          reflect.ValueOf(ctx),
          reflect.ValueOf(req),
          reflect.ValueOf(rsp),
      }
      ret := reflect.ValueOf(hdlr).MethodByName(methodName).Call(args)
    

3、难点:怎么兼容micro.Service的micro.WrapHandler、micro.AfterStop?

micro.WrapClient定义后,web.Service也是生效的,但micro.WrapHandler、micro.AfterStop并不生效。

wrappers := service.Options().Server.Options().HdlrWrappers
num := len(wrappers)
for i := 0; i < num; i++ {
  wrapper := wrappers[i]
  // type HandlerWrapper func(HandlerFunc) HandlerFunc
  // type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
  handlerFunc = wrapper(handlerFunc)
}

defer func() {
  tempReq := &Request{
    service: xconfig.ServiceName,
    method: methodName,
    contentType: md["content-type"],
    header: md,
    body : body,
    payload: req,
  }

  err := handlerFunc(ctx, tempReq, rsp)
  if err != nil {
    handleError(err, w)
  }
}()

3、遇到的坑:

context问题

{"id":"go.micro.client","code":408,"detail":"context canceled","status":"Request Timeout"}

调用日志:

image-20200717162802343

ChannelService.GetChannelData 接口日志:

12:12.188 [exec: 284] [timeout: 14.941199644s] [GRpc] Error meet.test.rpc.im : IMService.BatchGetLastMsg, error msg: {"id":"go.micro.client","code":408,"detail":"context canceled","status":"Request Timeout"}, Rsp: {} 12:12.196 [Info] /drone/src/handler/channel.go:922 ctx span: 14.648344255s

最终定位到问题:

func makeContext(r *http.Request) (context.Context, metadata.Metadata) {
    md, ok := metadata.FromContext(r.Context())
    if !ok {
        md = metadata.Metadata{}
    }

    // 设置客户端ip
    md["Remote"] = r.RemoteAddr
    for k, v := range r.Header {
        // 忽略Authorization,因为micro框架会用这个做鉴权
        if k == "Authorization" {
            k = "X-" + k
        }

        if len(v) == 1 {
            md[k] = v[0]
        } else {
            md[k] = strings.Join(v, "\n")
        }
    }

    // 巨坑,这里返回的是cancelCtx,浏览器/客户端可以触发 cancel
    // ctx := r.Context()
    ctx := context.Background()
    return metadata.NewContext(ctx, md), md
}

阿里OSS回调,请求头部带了 Authorization

自动触发框架的权限检查,通不过。解决方案:

        // 忽略Authorization,因为micro框架会用这个做鉴权
        if k == "Authorization" {
            k = "X-" + k
        }

grpc吃饱撑住,帮我recover,😓

  • 逐层返回,完全没有堆栈信息 image-20200723165721036

  • server grpc放在所有wrapper的下层 image-20200723170109343

解决方案: 最简单最繁琐做法:

func (g *General) TestCode(ctx context.Context, req *pb.TestCodeReq, rsp *empty.Empty) error {
  defer func(){ _handlerError(ctx, recover()) }()
    panic("just panic")
}

观察堆栈调用: image-20200723170315708

重新生成xxxx.micro.go文件,调用函数前,增加recover()

func (h *friendsHandler) DeleteFriend(ctx context.Context, in *DeleteFriendReq, out *empty.Empty) error {
    defer func() { _recoverHandler(ctx, recover()) }()
    return h.FriendsHandler.DeleteFriend(ctx, in, out)
}

4、失败的对象池优化

proto生成的代码:

func (o *i) GetMethodMap() map[string]func() (proto.Message, proto.Message) {
    return map[string]func() (proto.Message, proto.Message){
        "LoginWithMobile": func() (req proto.Message, rsp proto.Message) {
      // req = &LoginWithMobileReq{}
            req = poolLoginWithMobileReq.Get().(*LoginWithMobileReq)
            rsp = &LoginResp{}
            return
        },
    }
}

func (o *i) GetPutMap() map[string]func(proto.Message) {
    return map[string]func(proto.Message){
        "LoginWithMobile": func(req proto.Message) {
            poolLoginWithMobileReq.Put(req)
        },
}

var poolLoginWithMobileReq = sync.Pool{
    New: func() interface{} {
        return &LoginWithMobileReq{}
    },
}

common使用:

        req, rsp = MethodMap[methodName]()
        if len(body) > 0 {
            if isProtobuf(r) {
                err = proto.Unmarshal(body, req)
            } else {
                err = json.Unmarshal(body, req)
            }
        } else {
            req.Reset()
        }

本以为,req每次都是会被重置成最新数据,但遇到这个坑:

{"userID":1302,"sessionInfo":{"firstID":13053,"secondID":16796,"sessionType":2},"beginSeqId":1000200,"endSeqId":4611686018427388000,"limit":50,"isForward":true,"notReaded":true}

其中的isForward=true,noReaded=true 是来自之前的数据,json.Unmarshal(body, req) 空数据并没有将其覆盖。

Copyright © xinyan all right reserved,powered by Gitbook该文件修订时间: 2020-08-06 17:00:49

results matching ""

    No results matching ""