Code Monkey home page Code Monkey logo

droplet's Introduction

Droplet——一款轻量的Golang应用层框架

Go Report Card

如标题所描述的,Droplet 是一个 轻量中间层框架,何为中间层呢? 通常来说,我们的程序(注意这里我们仅仅讨论程序的范围,而非作为一个系统,因此这里不设计如 LB、Gateway、Mesh等内容,因为它们都处于程序以外)按不同的职责可以分为不同的层次,而按照不同的设计风格,常见的如下:

  • 三层架构:UIL(UserInterfaceLayer), BLL(BusinessLogicLayer), DAL(DataAccessLayer)
  • DDD分层架构(参考ddd-oriented-microservice):ApplicationLayer,DomainLayer,InfrastructureLayer
  • 洋葱架构(参考Onion Architecture ):Application, Infrastructure, ApplicationService, DomainService, DomainModel。

Tips

洋葱架构其实也是基于DDD的,它是DDD分层架构的升级版本。

但是今天我想用于解释中间层的架构并非以上的任何一种,它也源自于DDD的分层架构,不过我配合了六边形架构来说明它,分层图如下: image

在六边形架构中有个规则:依赖只能是由外部指向内部。 因此从外层到最内层分别是:

分层 职责
Access 程序的接入层(在六边形架构中这被称为输入适配器),通常位于整个请求 or 任务的起点,它可能是某种Web框架,也可能是一些队列的消费框架等。
Application 程序应用层,包含了一些非业务的逻辑,如:业务逻辑的编排、参数绑定、校验、请求日志、链路上报、状态读取等等
Domain & Utils 在最中心的地方我放入了两个层次描述:Domain 与 Utils,这两个分层都应该是位于依赖的最底层,意味着他们不应该引用本项目的其他层次。Domain层主要包含核心的业务逻辑,而Utils则是一些程序任何地方都可能会引用的代码段,比如常量定义、数据结构和语法糖等等
Infrastructure 基础设施层(在六边形架构中这被称为输出适配器),程序所有需要对外进行信息交换 or 功能依赖时都会放置在这一层实现,通常来说这些功能都是被依赖的那部分,因此我们如果要满足依赖约束的话,这里必须要引入 DIP(Dependency inversion principle),即在Application、Domain中定义依赖,而 Infrastructure 来实现它们,这样保证了它们是可被替换的

六边形架构优点在于解耦程序中业务无关的部分,以保证它们都是可被替换与扩展的。 而 Droplet 就工作在 Application 层,它的核心能力只有一个:提供基于pipeline的请求/响应处理能力。 可能有人会疑问,几乎每个框架都会实现类似的能力,为什么我们需要 Droplet 呢? 别急,我们来看看这些框架自带的 pipeline/middleware 存在什么弊端。 根据上面的架构图我们可以知道诸如 gin、go-restful、fasthttp 之类的http框架都是工作在 Access 层,因此框架自带的 pipeline/middleware 存在以下两个弊端:

  1. 框架绑定: 这个很容易理解,这些机制只能工作于特定的框架下,如果切换框架则需要需要调整代码,除了中间件的代码外,我时常也会见到程序在 API Handler 中耦合了大量框架相关的代码,比如:读取参数(header, query, body等)、根据业务结果回写响应等,这些代码渗透到了业务程序中(有时它们甚至会比业务代码占用了更多的行数),这加大了业务开发同学的维护成本,同时也降低了程序的可扩展性。

一些相关的BadCase

想象一下:

  • 你一直在使用 gin,但是有一天运营拿着数据找到你,说机器占用的成本太高了,而你发现只要切换到 fasthttp 就能为你带来更高的性能,但是从 gin -> fasthttp 你需要调整大量的 API handler 代码,这可太让人头疼了。
  • API handler中充斥了诸如 param, ok := req.Query("param") / param, ok := req.Header.Get("param") / err := xxx.Bind(req, &param) 之类的代码,这和业务毫无关系
  1. 没有请求/响应的结构化实体: 如果有开发过这些框架中间件的同学一定知道,大部分框架中间件的协议定义都是以 http.Request/httpResponse 为主体的,这意味着如果不做任何前置处理,你只能通过字节数组来感知 请求与响应 这在部分场景都不太方便,比如:根据请求、响应的结构体是否具备某些特征(比如接口)来执行某些特定的业务通用逻辑;又或者想在中间件中融入一些自动化的参数校验逻辑,因为你没有一个具体的结构化对象;再或者你不想要在每一个 API handler 中去设置一个响应的 Wrapper(通常它类似于 {code: 0, msg: "", data:{}}),想要在中间件去自动包装上它,也很难执行;最后就是——如果只依靠 http.Request/httpResponse,你也难在中间件感知到其他参与者的处理状态,。

相信我说的这些问题,使用过的同学应该都有所感触,而这些问题并非难以解决,它们中的大部分基本都是可以通过自行建立一套约定来得以缓解(比如将这些信息都通过 context 去获取),而 Droplet 也是诞生于我在过往团队中去克服这些问题的实践之中,是一个相对可靠的实现。

工作原理

带着上面提到的这些问题,我们来看看 Droplet 的工作原理是怎样的,如下图所示: image

如我所说的那样,Droplet 的核心在于 提供基于pipeline的请求/响应处理能力,因此我们可以看见这个图中涉及的所有模块都是基于 pipleline,可以说 Droplet 的所有能力都是由其扩展而来。 这里我们先介绍下图中出现的几个中间件(Middleware,这是组成 pipepine 关键元素):

  1. HttpInfoInjector: 注入 http 相关的一些信息,如 requestid, http.Request 等
  2. RespReshape: 根据 handler 的响应结果来进行一些调整,包括:发生错误时设置上默认的错误码、错误信息;如果缺少响应 wrapper 时包装上配置好的 wrapper
  3. HttpInput: 如果你设置了 API 的输入参数类型,那么该中间件会自动根据 Content-Typestruct tag 来读取对应的参数值,同时自动使用 validator 来检测参数错误
  4. TrafficLog: 如名字所示,如果你配置了响应的 logger,那么该中间件会执行日志记录。请注意该中间件工作在其他默认中间件的后面、你的handler之前,因此它统计的耗时是你业务函数的真正耗时,而不包含其他中间件的耗时时间,你可以考虑通过 网关Mesh 来记录完整的接口耗时。

Tips

  • Middleware 处理请求和响应顺序是相反的——即第一个处理请求的中间件它会是最后一个处理响应的。
  • 框架工作在应用层的优势有两点:
    • 与接入层框架解耦,保证绝项目代码可平滑 扩展/切换 其他接入层框架
    • 能够获取到结构化的接口 输入参数输出参数 你可以对其进行更具精细的切面操作

GetStart

这里以 Gin 为例,其他框架类似。

首先获取对应 wrapper 的 submodule:

go get github.com/shiningrush/droplet/wrapper/gin

// if you want to ensure the droplet is latest, you can get droplet 
go get github.com/shiningrush/droplet

然后程序代码如下:

package main

import (
	"reflect"

	"github.com/gin-gonic/gin"
	"github.com/shiningrush/droplet/core"
	"github.com/shiningrush/droplet/wrapper"
	ginwrap "github.com/shiningrush/droplet/wrapper/gin"
)

func main() {
	r := gin.Default()

    // 使用 wrapper 包装原始的 API
	r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{}))))
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

type JsonInput struct {
    // 从 path 读取, 并且为必须参数
	ID    string   `auto_read:"id,path" json:"id" validate:"required"`
    // 从 header 读取, 并且为必须参数
	User  string   `auto_read:"user,header" json:"user" validate:"required"`
    // 从 json unmarshal 后的ips字段读取
	IPs   []string `json:"ips"`
    // 从 json unmarshal 后的 count 字段读取
	Count int      `json:"count"`
    // 读取原始的 http body,接收参数类型必须为 []byte or io.ReadCloser
	Body  []byte   `auto_read:"@body"`
}

func JsonInputDo(ctx core.Context) (interface{}, error) {
	input := ctx.Input().(*JsonInput)

	return input, nil
}

参数绑定

Usage 一节中所展示的,我们可以通过 wrapper.InputType 选项来告诉 Droplet 是否期望自动化进行参数绑定,如果某些场景下你不需要从 Body 进行自动的参数绑定了,可以通过显式的选项来禁止它,如:

r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{}), wrapper.DisableUnmarshalBody())))

参数绑定的Tag格式如下:

auto_read: ({key},{source}) or @body

其中值如下:

  • key: 用于到各个来源中匹配对应值
  • source: 可选值有 query, header, path, body(缺省默认)
  • @body: 特殊的取值,意味着获取原生的body作为字段值,此时你的字段类型应该为 []byte or io.ReadCloser

同时 Droplet 会自动使用 validator 对入参进行校验,因此你可以使用其 tag 来辅助验证参数合法性。

响应整形

通常来说API都会在响应的最外层进行一层包装,比如 Droplet 自带的 wrapper 如下所示:

{
    "code": 0,       // API 错误码
    "message": "",   // API 消息
    "data": {},      // 响应数据
    "request_id": "" // 请求ID
}

当然你可以完全去掉这个默认 Wrapper 或者 使用满足你们团队规范的 Wrapper(需要实现 data.HttpResponse 接口) 来替换它:

type NativeJsonResp struct {
	data interface{}
}

func (n *NativeJsonResp) Set(code int, msg string, data interface{}) {
	n.data = data
}

func (n *NativeJsonResp) SetReqID(reqId string) {
}

func (n *NativeJsonResp) MarshalJSON() ([]byte, error) {
	return json.Marshal(n.data)
}

func main() {
    ...
	droplet.Option.ResponseNewFunc = func() data.HttpResponse {
		return &NativeJsonResp{}
	}
    ...
}

对于另外一些并不需要 Wrapper 或者 你想要自行控制返回的内容时可以在 Handler 中使用一些实现了特定接口的返回值,如下所示:

func GetLoginQRCode(ctx droplet.Context) (interface{}, error) {
	type makeQRCodeResp struct {
		SceneID string `json:"scene_id"`
		State   int    `json:"state"`
		Url     string `json:"url"`
	}

	var resp makeQRCodeResp
	if err := goreq.Get(UrlMakeQRCode, goreq.SetHeader(fakeClientHeader()), goreq.JsonResp(&resp)).Do(); err != nil {
		return nil, fmt.Errorf("get qrcode failed: %w", err)
	}

	return &data.RawResponse{
		StatusCode: http.StatusOK,
		Body:       []byte(fmt.Sprintf(QRCodeBase, resp.SceneID, resp.SceneID, resp.Url)),
	}, nil
}

类似的还有 data.FileResponsedata.SpecCodeResponse,根据其名字你可以在需要的场景选择它们。

同时在整形过程中,为了业务研发不再需要关心错误处理,Droplet 会自动将 err != nil 的响应转化到 codemessage 字段上。 如下图所示:

func ErrorAPI(ctx droplet.Context) (interface{}, error) {
    return nil, errors.New("failed")
}

那么你将得到如下的响应:

{
    "code": 10000,
    "message": "failed"
}

当然,你可以使用 data.BaseError 来指定你想返回的错误码:

func ErrorAPI(ctx droplet.Context) (interface{}, error) {
    return nil, data.BaseError{Code: 100, Message: "custom message"}
}

Tips

  • 这些特定的响应其背后都是实现了某一类接口,如果有需要你也完全可以自行实现。

流量记录

Droplet 自带了记录 API 出参与入参的能力,但是默认所有记录信息都会被抛弃,如果想要启用它,你需要实现 Droplet 的全局 Logger,如下所示:

import (
    "github.com/shiningrush/droplet/log"
)

func main() {
    ...
    // CustomLogger 需要实现 log.Interface
	log.DefLogger = &CustomLogger{}

    // droplet 默认只会记录 Path,Method,耗时等信息,如果你需要打印 API 的输入与输出,可以在全局选项中开启(在Wraps函数中也可指定)
    droplet.Option.TrafficLogOpt = &middleware.TrafficLogOpt{
		LogReq:  true,
		LogResp: true,
	}
    ...
}

自定义中间件

实现一个自定义中间件很简单,你只需要实现与 Hanler 类似的接口即可,下图是一个简单的中间件,它会用于检测输入参数是否需要 Quota 并执行相关逻辑:

type DemoMiddleware struct {
    // 继承基本的middleware,里面有用于实现处理链路的公共逻辑
    middleware.BaseMiddleware
}

func (mw *HttpInputMiddleware) Handle(ctx core.Context) error {
    if ck, ok := ctx.Input().(QuotaChecker); !ok {
        if err := ck.IsQuotaEnough(); err != nil {
            return err
        }
    }

    // 调用下一个中间件,有需要的话你也可以在响应返回后执行部分逻辑
    return mw.Handle(ctx)
}

func main() {
    // 如果你需要所有API都添加该中间件,可以在全局选项中将你的中间件编排
	droplet.Option.Orchestrator = func(mws []core.Middleware) []core.Middleware {
		return append(mws, &DemoMiddleware{})
	}

    ...
    // 在单个API上启用
	r.POST("/json_input/:id", ginwrap.Wraps(APIHandler,
        wrapper.Orchestrator(func(mws []core.Middleware) []core.Middleware {
			return append(mws, &DemoMiddleware{})
	})))
    ...
}

Tips

Q: 为什么使用 Orchestrator 这样的形式来配置中间件,而非通过 Priorty 之类的权重来实现中间件的编排,这样在未来可以做到通过配置文件来调整中间件

A: 主要出于几个考虑

  1. 考虑现代微服务的架构下,多数业务无关的通用能力都会下沉到网关以及Mesh,因此一个服务的切面不会太多,在通过这样的方式来配置,成本是可以接受的。
  2. 通过 Orchestrator 方式,用户还可以任意操作已添加的中间件,比如移除一些不必要的中间件,这是权重的方式无法做到的。
  3. 当然如果以后有需要,现在的设计并不妨碍我们支持基于权重的方式

小结

正如文中所说,Droplet 的核心目标是 提供位于应用层的、pipeline 形式的请求处理能力,并以此为基础提供了一些开箱即用的中间件。 它对项目带来的收益总结为几点:

  • 提供了框架无关的请求处理能力,这使得我们的服务更具韧性
  • 在应用层我们可以接触到 已序列化后的接口输入 以及 尚未序列化的接口输出 ,这使得我们在离业务更近的地方进行切面操作,进而将更多的通用代码沉淀到切面而降低业务代码的复杂度,更聚焦业务逻辑。

希望 Droplet 能对你有所帮助与启发。

droplet's People

Contributors

dependabot[bot] avatar nic-chen avatar philhuan avatar shiningrush avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

droplet's Issues

missing method GetHeader

go/pkg/mod/github.com/shiningrush/droplet/wrapper/[email protected]/gin.go:13:20: cannot use ctx.Writer (variable of type "github.com/gin-gonic/gin".ResponseWriter) as data.ResponseWriter value in struct literal: "github.com/gin-gonic/gin".ResponseWriter does not implement data.ResponseWriter (missing method GetHeader)

bug: get request occurs error in binding query param

version:
github.com/shiningrush/droplet v0.3.0
github.com/shiningrush/droplet/wrapper/gin v0.2.1

phenomenon
when a requst use get method carry query param. it's param binding occurs error. different param order appears different phenomenon.
the following is my test:

  1. this is my struct
    `

    type GetTestInput struct {
     PageSize   int `json:"pageSize" form:"pageSize" auto_read:"pageSize"`
     PageNumber int `json:"pageNum" form:"pageNum" auto_read:"pageNum"`
     ClusterId  int    `auto_read:"ClusterId,header" json:"ClusterId"`
     InnerAddr  string `auto_read:"innerAddr,query" json:"innerAddr"`
     OutterAddr string `auto_read:"outterAddr,query" json:"outterAddr"`
     Path       string `auto_read:"path,query" json:"path"`
     Status     *int   `auto_read:"status,query" json:"status"`
     Labels     string `auto_read:"labels,query" json:"labels"`
     HomeUnit   *int   `auto_read:"homeUnit,query" json:"homeUnit"`
     }
    

`
when I use the above struct and send the following request:
(the ClusterId is in header)
http://127.0.0.1:8080/api/v1/test?labels=aaa&pageNum=1&pageSize=50&homeUnit=1
image

when I use the following struct(it just change the property order ):
`

   type GetTestInput struct {
        Labels     string `auto_read:"labels,query" json:"labels"`
    PageSize   int `json:"pageSize" form:"pageSize" auto_read:"pageSize"`
    PageNumber int `json:"pageNum" form:"pageNum" auto_read:"pageNum"`
    ClusterId  int    `auto_read:"ClusterId,header" json:"ClusterId"`
    InnerAddr  string `auto_read:"innerAddr,query" json:"innerAddr"`
    OutterAddr string `auto_read:"outterAddr,query" json:"outterAddr"`
    Path       string `auto_read:"path,query" json:"path"`
    Status     *int   `auto_read:"status,query" json:"status"`
    HomeUnit   *int   `auto_read:"homeUnit,query" json:"homeUnit"`
    }

`
then I use the above struct and send the following request:
http://127.0.0.1:8080/api/v1/test?labels=aaa&pageNum=1&pageSize=50&homeUnit=1
it appears as below:
image

please pay attention to "labels", just change the order in the same struct. one could get it's value. the other don't

I want to know why this occurs.

Feat: need a way to get request body

Hello. Now there is no way to get the request body in gin's hander. because you read the body to inject field.
I think it's needed to save the request body for other use.

IMO there are two ways to do this:

  1. Set the dict with the request body when reading the body.
  2. Use a buffer to save the request body and implate the http.Requests GetBody function.

panic when upload file

struct:

type ImportInput struct {
	Force byte `auto_read:"force,query"`
	FileName string `auto_read:"_file"`
	FileContent []byte `auto_read:"file"`
}

error log:

reflect: call of reflect.Value.Set on zero Value
[Recovery] 2021/01/27 - 08:47:31 panic recovered:

reflect: call of reflect.Value.Set on zero Value
/usr/local/Cellar/go/1.15.2/libexec/src/reflect/value.go:234 (0x10a0c2f)
	flag.mustBeExportedSlow: panic(&ValueError{methodNameSkip(), Invalid})
/usr/local/Cellar/go/1.15.2/libexec/src/reflect/value.go:228 (0x10a6cd5)
	flag.mustBeExported: f.mustBeExportedSlow()
/usr/local/Cellar/go/1.15.2/libexec/src/reflect/value.go:1549 (0x10a6cbb)
	Value.Set: x.mustBeExported() // do not let unexported x leak
/Users/test/data/www/droplet/middleware/http_input.go:161 (0x1635a5c)
	(*HttpInputMiddleware).injectFieldFromUrlAndMap: input.Field(i).Set(reflect.ValueOf(tarVal))
/Users/test/data/www/droplet/middleware/http_input.go:63 (0x1634789)
	(*HttpInputMiddleware).Handle: if err := mw.injectFieldFromUrlAndMap(pInput); err != nil {
/Users/test/data/www/droplet/middleware/base.go:16 (0x16368e1)
	(*BaseMiddleware).Handle: return mw.next.Handle(ctx)
/Users/test/data/www/droplet/middleware/http_resp_reshap.go:19 (0x16368af)
	(*HttpRespReshapeMiddleware).Handle: if err := mw.BaseMiddleware.Handle(ctx); err != nil {
/Users/test/data/www/droplet/middleware/base.go:16 (0x19268f0)
	(*BaseMiddleware).Handle: return mw.next.Handle(ctx)
/Users/test/data/www/apisix-dashboard/api/internal/filter/authentication.go:90 (0x19268be)
	(*AuthenticationMiddleware).Handle: return mw.BaseMiddleware.Handle(ctx)
/Users/test/data/www/droplet/middleware/base.go:16 (0x18e0dea)
	(*BaseMiddleware).Handle: return mw.next.Handle(ctx)
/Users/test/data/www/apisix-dashboard/api/internal/handler/handler.go:78 (0x18e0dc1)
	(*ErrorTransformMiddleware).Handle: if err := mw.BaseMiddleware.Handle(ctx); err != nil {
/Users/test/data/www/droplet/middleware/base.go:16 (0x163446b)
	(*BaseMiddleware).Handle: return mw.next.Handle(ctx)
/Users/test/data/www/droplet/middleware/http_injector.go:28 (0x1634476)
	(*HttpInfoInjectorMiddleware).Handle: return mw.BaseMiddleware.Handle(ctx)
/Users/test/data/www/droplet/pipe.go:84 (0x138c602)
	(*BasePipe).Run: err := p.mws[0].Handle(initCtx)
/Users/test/go/pkg/mod/github.com/shiningrush/droplet/wrapper/[email protected]/gin.go:41 (0x16385c4)
	Wraps.func1: Run(handler, droplet.InitContext(dCtx))
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/data/www/apisix-dashboard/api/internal/filter/recover.go:53 (0x192a5cd)
	RecoverHandler.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/data/www/apisix-dashboard/api/internal/filter/schema.go:193 (0x192b3e7)
	SchemaCheck.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/data/www/apisix-dashboard/api/internal/filter/logging.go:35 (0x19294de)
	RequestLogHandler.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/data/www/apisix-dashboard/api/internal/filter/request_id.go:41 (0x192a8a6)
	RequestId.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/data/www/apisix-dashboard/api/internal/filter/cors.go:31 (0x1929286)
	CORS.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/go/pkg/mod/github.com/gin-contrib/[email protected]/sessions.go:52 (0x191c8c5)
	Sessions.func1: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x1620f1a)
	(*Context).Next: c.handlers[c.index](c)
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:409 (0x162af39)
	(*Engine).handleHTTPRequest: c.Next()
/Users/test/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:367 (0x162a62c)
	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/Cellar/go/1.15.2/libexec/src/net/http/server.go:2843 (0x130c342)
	serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/Cellar/go/1.15.2/libexec/src/net/http/server.go:1925 (0x1307a4c)
	(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.15.2/libexec/src/runtime/asm_amd64.s:1374 (0x1071540)
	goexit: BYTE	$0x90	// NOP

Why not use *gin.Context in gin wrapper ??

I see the wraps code use request.Context() like this dCtx.SetContext(input.Req.Context()) how do id use the gin context values set by gin middlewares ?? this is really not a design problem, right?

对代码写法的疑问

ret, _ := droplet.NewPipe().
	Add(middleware.NewHttpInfoInjectorMiddleware(middleware.HttpInfoInjectorOption{
		ReqFunc: func() *http.Request {
			return ctx.Request
		},
	})).
	Add(middleware.NewRespReshapeMiddleware()).
	Add(middleware.NewHttpInputMiddleWare(middleware.HttpInputOption{
		PathParamsFunc: func(key string) string {
			return ctx.Param(key)
		},
		InputType:      opt.InputType,
		IsReadFromBody: opt.IsReadFromBody,
	})).
	Add(middleware.NewTrafficLogMiddleware(opt.TrafficLogOpt)).
	SetOrchestrator(opt.Orchestrator).
	Run(handler, droplet.InitContext(dCtx))

这里为什么要用"生成器模式"呢?而不是下面这样呢?

ret, _ := droplet.NewPipe()
ret.Add(middleware.NewHttpInfoInjectorMiddleware(middleware.HttpInfoInjectorOption{
  ReqFunc: func() *http.Request {
	  return ctx.Request
  },
  }))
...

Feat: droplet.Context can save the gin.Context

In some situations, we need to use gin.Context to save the key and value in the middleware.But now, I can't find a way to get the gin.Context though droplect.Context.

See :

dCtx.SetContext(ctx.Request.Context())

So I recommend adding a field to save the gin.Context.Then we can get the gin.Context in our interface handler.

How to access Keys of gin.Context?

Hi, I set the user infomation in a filter of gin, the filter is as follows:

func Authentication() gin.HandlerFunc {
    return func(c *gin.Context){
        userId := "123"
        c.Set("userId", userId)
        c.Next()
    }
}

How should I get the value of userId?

func (h *Handler) current(c droplet.Context) (interface{}, error){
    userId := ????
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.