Lura使用初探
Lura是KrankenD的核心框架,虽然KranKenD是一个商业API网关,但是他同样提供了开源版本,KrankenD是一个功能完整的网关,而Lura只是一个框架,Lura的文档内容也很简陋。好在KrankenD的功能文档比较多,可以用作参照
· 三元组数据,tsv格式
# {数据集}_train.tsv/{数据集}_dev.tsv
头实体<\t>关系<\t>尾实体<\n>
· 实体关系对应文本数据,tsv格式
# {数据集}_entity2text.tsv/{数据集}_relation2text.tsv
实体(关系)<\t>实体(关系)对应文本<\n>
KrankenD演示
https://github.com/krakendio/playground-community
The KrakenD Playground is a demonstration environment that puts together the necessary pieces to get you started with our API Gateway, using example use-cases. ![[images/Pasted image 20221027153225.png]]
Makefile
不管是Lura还是KrankenD都提供了一个Makefile用于 生成可执行文件,但是Lura的Makefile仅仅只是生成一个测试用的软件,意义不是很大。
.PHONY: all test build benchmark
OS := $(shell uname | tr '[:upper:]' '[:lower:]')
GIT_COMMIT := $(shell git rev-parse --short=7 HEAD)
all: test build
generate:
    go generate ./...
    go build -buildmode=plugin -o ./transport/http/client/plugin/tests/lura-client-example.so ./transport/http/client/plugin/tests
    go build -buildmode=plugin -o ./transport/http/server/plugin/tests/lura-server-example.so ./transport/http/server/plugin/tests
    go build -buildmode=plugin -o ./proxy/plugin/tests/lura-request-modifier-example.so ./proxy/plugin/tests/logger
    go build -buildmode=plugin -o ./proxy/plugin/tests/lura-error-example.so ./proxy/plugin/tests/error
test: generate
    go test -cover -race ./...
    #go test -tags integration --coverpkg=./... ./test/...
    go test -tags integration ./transport/...
    go test -tags integration ./proxy/...
    
benchmark:
    @mkdir -p bench_res
    @touch bench_res/${GIT_COMMIT}.out
    @go test -run none -bench . -benchmem ./... >> bench_res/${GIT_COMMIT}.out
build:
    go build ./...
而KranKenD的Makefile提供了一个相当全面的生成各个二进制文件的示例。如果致力于使用Lura从头开始开发网关的话,可以参考这个文件编写Makefile
Library Usage
The Lura project is presented as a Go library that you can include in your own Go application to build a powerful proxy or API gateway.
    package main
    import (
        "flag"
        "log"
        "os"
        "github.com/luraproject/lura/config"
        "github.com/luraproject/lura/logging"
        "github.com/luraproject/lura/proxy"
        "github.com/luraproject/lura/router/gin"
    )
    func main() {
        port := flag.Int("p", 0, "Port of the service")
        logLevel := flag.String("l", "ERROR", "Logging level")
        debug := flag.Bool("d", false, "Enable the debug")
        configFile := flag.String("c", "/etc/lura/configuration.json", "Path to the configuration filename")
        flag.Parse()
        parser := config.NewParser()
        serviceConfig, err := parser.Parse(*configFile)
        if err != nil {
            log.Fatal("ERROR:", err.Error())
        }
        serviceConfig.Debug = serviceConfig.Debug || *debug
        if *port != 0 {
            serviceConfig.Port = *port
        }
        logger, _ := logging.NewLogger(*logLevel, os.Stdout, "[LURA]")
        routerFactory := gin.DefaultFactory(proxy.DefaultFactory(logger), logger)
        routerFactory.New().Run(serviceConfig)
    }
Important Concepts
endpoint & backend
endpoints是聚合后的API,backend是后端服务原本提供的API
By default KrakenD only works with RESTful URL patterns to connect to backends.
{
  "endpoints": [
    {
      "endpoint": "/v1/users/{user}",
      "method": "GET",
      "backend": [
        {
          "url_pattern": "/users/summary/{user}",
          "method": "GET",
          "host": [
            "https://api.mybackend.com"
          ]
        }
      ]
    }
  ]
}
service
指的是网关服务器提供的服务,如果不设置output_encoding为no-op,网关服务默认不作为反向代理服务。
Important Packages
config
定义ServiceConfig的结构,以及基于viper的配置文件解析器的接口和实现,包括URI的解析方法。 其中ServiceConfig就是Lura网关中最重要的配置文件的结构定义,基本上在配置文件就完全阐明了对应的Lura网关能做的事,所以使用Lura就需要了解各种功能在配置文件中是如何体现的。
在这里定义的大多是一些全局性的配置。
配置文件可以在 https://designer.krakend.io/#!/service 生成,这是krankenD的配置文件在线生成器,所以有些功能可能Lura框架没有。
router
路由器层负责设置HTTP(S)服务,绑定ServiceConfig结构中定义的端点,并在将任务委托给内层(代理)之前将HTTP请求转换为代理请求。一旦内部代理层返回代理响应,路由器层将其转换为适当的HTTP响应并将其发送给用户。
这一层是可扩展的,可以使用自己选择的HTTP router, 框架,或者中间件,他提供了一个接口以及几个实现。包括基于 net/http的mux 路由器和gin框架提供的httprouter。
需要注意的是Lura框架目前几乎不支持其他协议(Thrift, gRPC, AMQP, NATS)
// Router sets up the public layer exposed to the users
type Router interface {
   Run(config.ServiceConfig)
}
// RouterFunc type is an adapter to allow the use of ordinary functions as routers.
// If f is a function with the appropriate signature, RouterFunc(f) is a Router that calls f.
type RouterFunc func(config.ServiceConfig)
// Run implements the Router interface
func (f RouterFunc) Run(cfg config.ServiceConfig) { f(cfg) }
// Factory creates new routers
type Factory interface {
    New() Router
    NewWithContext(context.Context) Router
}
proxy
包括各种proxy和proxy 中间件的接口和实现。 这一层做的就是将路由器层接受的请求拆分转化为对后端服务的多个请求,处理响应并返回单个响应。 中间件生成自定义的代理,这些代理根据配置中的工作流进行链式执行。 Lura框架提供了代理堆栈工厂的默认实现。 中间件:
- The balancingmiddleware uses some type of strategy for selecting a backend host to query.
- The concurrentmiddleware improves the QoS by sending several concurrent requests to the next step of the chain and returning the first succesful response using a timeout for canceling the generated workload.
- The loggingmiddleware logs the received request and response and also the duration of the segment execution.
- The mergingmiddleware is a fork-and-join middleware. It is intended to split the process of the request into several concurrent processes, each one against a different backend, and to merge all the received responses from those created pipes into a single one. It applies a timeout, as theconcurrentone does.
- The httpmiddleware completes the received proxy request by replacing the parameters extracted from the user request in the definedURLPattern. 代理:
- The httpproxy translates a proxy request into an HTTP one, sends it to the backend API using aHTTPClientFactory, decodes the returned HTTP response with aDecoder, manipulates the response data with anEntityFormatterand returns it to the caller.
Important Featrues
Data manipulation
Incompatible with the
no-opencoding.
可以对backend的数据进行过滤(使用Allow列表和Deny列表),分组,映射,捕获,数组处理。值得注意的是,对单个Object和对列表的处理并不相同,不能混淆。 ![[images/Pasted image 20221027153358.png]]
黑白名单
Allow列表和Deny列表在旧版本中被称为黑白名单,但这并不确切。 黑名单很简单,就是过滤掉黑名单中的字段,白名单的意思却是只显示白名单中的字段。
支持嵌套,可以使用
.作为层级分隔符,例如在{ "a": { "a1": 1 } }中的a1字段可以用a.a1来指定。
example
{
  "endpoint": "/posts/{user}",
  "method": "GET",
  "backend": [{
    "url_pattern": "/posts/{user}",
    "host": [
      "https://jsonplaceholder.typicode.com"
    ],
    "allow": [
      "id",
      "title"
    ]
  }]
}
过滤后的响应为
{
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
}
黑白名单显而易见是不兼容的,但是从文档来看,同时使用这两者会出现未知错误。 严格意义上来说,黑名单更快一点。
分组
谈到API聚合的问题,很容易想到的就是键冲突,而将不同的backend响应封装到不同的group里就是一种解决方法。
{
    "endpoint": "/users/{user}",
    "method": "GET",
    "backend": [
      {
        "url_pattern": "/users/{user}",
        "host": ["https://jsonplaceholder.typicode.com"]
      },
      {
        "url_pattern": "/posts/{user}",
        "host": ["https://jsonplaceholder.typicode.com"],
        "group": "last_post"
      }
    ]
  }
{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "Sincere@april.biz",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874",
    "geo": {
      "lat": "-37.3159",
      "lng": "81.1496"
    }
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org",
  "company": {
    "name": "Romaguera-Crona",
    "catchPhrase": "Multi-layered client-server neural-net",
    "bs": "harness real-time e-markets"
  },
  "last_post": {
    "id": 1,
    "userId": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  }
}
映射
严格来说,这个功能其实就是字段重命名
{
  "endpoint": "/users/{user}",
  "method": "GET",
  "backend": [
    {
      "url_pattern": "/users/{user}",
      "host": [
        "https://jsonplaceholder.typicode.com"
      ],
      "mapping": {
        "email": "personal_email"
      }
    }
  ]
}
这样在响应中,email字段就会被改变为personal_email
捕获(target)
实际上干的是一个拆包装的活,有点像白名单,过滤掉其他字段,只留下要捕获的字段内容,并将这个字段中的内容拆出来,放到JSON顶层 例子
{
    "endpoint": "/foo",
    "method": "GET",
    "backend": [
      {
        "url_pattern": "/bar",
        "target": "data"
      }
    ]
  }
原来的响应数据
{
  "apiVersion":"2.0",
  "data": {
    "updated":"2010-01-07T19:58:42.949Z",
    "totalItems":800,
    "startIndex":1,
    "itemsPerPage":1,
    "items":[]
  }
}
捕获后的响应数据
{
  "updated":"2010-01-07T19:58:42.949Z",
  "totalItems":800,
  "startIndex":1,
  "itemsPerPage":1,
  "items":[]
}
数组处理
分为两类,一个是响应的内容跟就是一个数组,可以添加"is_collection": true ,KrankenD会自动将数组包装到一个{collections:[]}中去,这个Key可以用Mapping重命名。
另一类是希望处理内部的数组,提供了三种方式,一个是内置的flatmap,一个是通过插件Response modifier plugins高性能但是需要编译 ,一个是通过Lua脚本,不需要编译但是低性能
Service dicovery
这玩意没有服务发现功能,他所谓的服务发现完全是静态服务发现,要么是在配置文件里写好了,要么是使用DNS SRV 连接外部服务发现提供商,
DNS SRV
这是一个Kubernetes, Mesos, Haproxy, Nginx plus, AWS ECS, Linkerd等共同建立的市场标准,是用于建立servic之间的连接的DNS记录格式
The format of the SRV record is as follows:
_service._proto.name. ttl IN SRV priority weight port target
Example. A service running on port 8000 with maximum priority (0) and a weight 5 ):
_api._tcp.example.com. 86400 IN SRV 0 5 8000 foo.example.com.
使用
{
    "backend": [
        {
            "url_pattern": "/foo",
            "sd": "dns",
            "host": [
                "api-catalog.service.consul.srv"
            ],
            "disable_host_sanitize": true
        }
    ]
}