如何在Go中为Lambda中间件创建通用类型

huangapple go评论134阅读模式
英文:

how to create a generic type for lambda middleware in go

问题

我正在使用AWS Lambda中的Go语言,并且正在寻找一个通用的中间件解决方案。我有以下代码:

func WsHandler(ctx context.Context, event events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
}

type HandlerFunc func(context.Context, events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error)

func LogMiddleware(next HandlerFunc) HandlerFunc {
	return HandlerFunc(func(ctx context.Context, event events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
		
		return next(ctx, event)
	})
}

lambda.Start(LogMiddleware(WsHandler))

中间件函数有一个参数events.APIGatewayWebsocketProxyRequest,因为目标处理程序WsHandler使用了这个类型。

我有另一个处理程序,它接受参数event events.APIGatewayProxyRequest,如下所示。它无法使用这个中间件,因为参数不匹配。

GraphqlQueryMutationHandler(ctx context.Context, event events.APIGatewayProxyRequest){
...
}

我尝试将中间件处理函数更改为interface{},但它不起作用。Go语言会报错,指出这个类型不正确。

type HandlerFunc func(context.Context, interface{}) (interface{}, error)

有没有办法使中间件适用于任何处理程序类型?

英文:

I am using go in AWS lambda and looking for a generic middleware solution. I have below code:

func WsHandler(ctx context.Context, event events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
}

type HandlerFunc func(context.Context, events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error)

func LogMiddleware(next HandlerFunc) HandlerFunc {
	return HandlerFunc(func(ctx context.Context, event events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
		
		return next(ctx, event)
	})
}

lambda.Start(LogMiddleware(WsHandler))

The middleware function has a parameter events.APIGatewayWebsocketProxyRequest because the target handler WsHandler uses this type.

I have another handler which takes parameter event events.APIGatewayProxyRequest as below. It can't use this middleware since the parameter doesn't match.

GraphqlQueryMutationHandler(ctx context.Context, event events.APIGatewayProxyRequest){
...
}

I have tried to change the middleware handle to interface{}, but it doesn't work. go complains this type.

type HandlerFunc func(context.Context, interface{}) (interface{}, error)

Is there any way to make the middleware works for any handler type?

答案1

得分: 1

让我分享一下我在我的系统上能够复制的工作解决方案。首先,我将与您分享我使用的项目布局:

events/
  http_event.json
  sqs_event.json
hello-world/
  main.go
sqs/
  main.go
middlewares/
  middlewares.go

现在,让我们关注代码部分。

middlewares/middlewares.go

代码如下:

package middlewares

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/events"
)

type Record struct {
	events.APIGatewayProxyRequest `json:",omitempty"`
	events.SQSEvent               `json:",omitempty"`
}

type Event struct {
	Records []Record `json:"records"`
}

type HandlerFunc func(ctx context.Context, event Event) (string, error)

func LogMiddleware(ctx context.Context, next HandlerFunc) HandlerFunc {
	return HandlerFunc(func(ctx context.Context, event Event) (string, error) {
		fmt.Println("log from middleware!")
		return next(ctx, event)
	})
}

让我们总结一下基本概念:

  • 我们定义了Event结构体,它将是我们的通用事件。它是Record结构体的包装器。
  • Record结构体使用结构体嵌入来嵌入我们要处理的所有事件(例如event.APIGatewayProxyRequestSQSEvent)。
  • 我们在中间件的签名中依赖于这一点,以使其尽可能通用。

events/http_event.json

{
    "records": [
        {
            "body": "{\"message\": \"hello world\"}",
            "resource": "/hello",
            "path": "/hello",
            "httpMethod": "GET",
            "isBase64Encoded": false,
            "queryStringParameters": {
                "foo": "bar"
            },
            "pathParameters": {
                "proxy": "/path/to/resource"
            },
            "stageVariables": {
                "baz": "qux"
            },
            "headers": {
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                "Accept-Encoding": "gzip, deflate, sdch",
                "Accept-Language": "en-US,en;q=0.8",
                "Cache-Control": "max-age=0",
                "CloudFront-Forwarded-Proto": "https",
                "CloudFront-Is-Desktop-Viewer": "true",
                "CloudFront-Is-Mobile-Viewer": "false",
                "CloudFront-Is-SmartTV-Viewer": "false",
                "CloudFront-Is-Tablet-Viewer": "false",
                "CloudFront-Viewer-Country": "US",
                "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
                "Upgrade-Insecure-Requests": "1",
                "User-Agent": "Custom User Agent String",
                "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
                "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
                "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
                "X-Forwarded-Port": "443",
                "X-Forwarded-Proto": "https"
            },
            "requestContext": {
                "accountId": "123456789012",
                "resourceId": "123456",
                "stage": "prod",
                "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
                "requestTime": "09/Apr/2015:12:34:56 +0000",
                "requestTimeEpoch": 1428582896000,
                "identity": {
                    "cognitoIdentityPoolId": null,
                    "accountId": null,
                    "cognitoIdentityId": null,
                    "caller": null,
                    "accessKey": null,
                    "sourceIp": "127.0.0.1",
                    "cognitoAuthenticationType": null,
                    "cognitoAuthenticationProvider": null,
                    "userArn": null,
                    "userAgent": "Custom User Agent String",
                    "user": null
                },
                "path": "/prod/hello",
                "resourcePath": "/hello",
                "httpMethod": "GET",
                "apiId": "1234567890",
                "protocol": "HTTP/1.1"
            }
        }
    ]
}

这里没有什么重要的要说的。

events/sqs_event.json

{
    "records": [
        {
            "Records": [
                {
                    "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
                    "receiptHandle": "MessageReceiptHandle",
                    "body": "My own event payload!",
                    "attributes": {
                        "ApproximateReceiveCount": "1",
                        "SentTimestamp": "1523232000000",
                        "SenderId": "123456789012",
                        "ApproximateFirstReceiveTimestamp": "1523232000001"
                    },
                    "messageAttributes": {},
                    "md5OfBody": "4d1d0024b51659ad8c3725f9ba7e2471",
                    "eventSource": "aws:sqs",
                    "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
                    "awsRegion": "us-east-1"
                }
            ]
        }
    ]
}

这里也是一样的。

hello-world/main.go

package main

import (
	"context"
	"fmt"

	"httplambda/middlewares"

	"github.com/aws/aws-lambda-go/lambda"
)

func lambdaHandler(ctx context.Context, event middlewares.Event) (string, error) {
	_ = ctx
	fmt.Println("Path:", event.Records[0].APIGatewayProxyRequest.Path)

	fmt.Println("Hi from HTTP-triggered lambda!")

	return "", nil
}

func main() {
	// start the lambda handler
	lambda.Start(middlewares.LogMiddleware(context.Background(), lambdaHandler))
}

请注意我们如何访问事件的信息。

sqs/main.go

package main

import (
	"context"
	"fmt"

	"httplambda/middlewares"

	"github.com/aws/aws-lambda-go/lambda"
)

func lambdaHandler(ctx context.Context, event middlewares.Event) (string, error) {
	_ = ctx
	fmt.Println("Queue name:", event.Records[0].SQSEvent.Records[0].EventSourceARN)
	fmt.Println("Hi from SQS-triggered lambda!")
	return "", nil
}

func main() {
	lambda.Start(middlewares.LogMiddleware(context.Background(), lambdaHandler))
}

最后

有几点需要考虑:

  1. 在采用这个解决方案之前,我尝试使用类型参数,但没有成功。似乎在中间件的签名中不允许使用类型参数。
  2. 代码过于简化,不适合生产环境。

如果这对您有帮助,或者您需要其他任何帮助,请告诉我,谢谢!

英文:

let me share the working solution I was able to replicate on my system. First, I'm going to share with you the project layout I used:

events/
  http_event.json
  sqs_event.json
hello-world/
  main.go
sqs/
  main.go
middlewares/
  middlewares.go

Now, let's focus on the code.

middlewares/middlewares.go

The code is as follows:

package middlewares

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/events"
)

type Record struct {
	events.APIGatewayProxyRequest `json:",omitempty"`
	events.SQSEvent               `json:",omitempty"`
}

type Event struct {
	Records []Record `json:"records"`
}

type HandlerFunc func(ctx context.Context, event Event) (string, error)

func LogMiddleware(ctx context.Context, next HandlerFunc) HandlerFunc {
	return HandlerFunc(func(ctx context.Context, event Event) (string, error) {
		fmt.Println("log from middleware!")
		return next(ctx, event)
	})
}

Let's summarize the fundamental concepts:

  • We define the Event struct that will be our generic event. It's a wrapper around the Record struct.
  • The Record struct uses the struct embedding to embed all of the events that we're going to handle (e.g. event.APIGatewayProxyRequest and SQSEvent).
  • We rely on this in the middleware's signature to be as much generic as we can.

events/http_event.json

{
    "records": [
        {
            "body": "{\"message\": \"hello world\"}",
            "resource": "/hello",
            "path": "/hello",
            "httpMethod": "GET",
            "isBase64Encoded": false,
            "queryStringParameters": {
                "foo": "bar"
            },
            "pathParameters": {
                "proxy": "/path/to/resource"
            },
            "stageVariables": {
                "baz": "qux"
            },
            "headers": {
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                "Accept-Encoding": "gzip, deflate, sdch",
                "Accept-Language": "en-US,en;q=0.8",
                "Cache-Control": "max-age=0",
                "CloudFront-Forwarded-Proto": "https",
                "CloudFront-Is-Desktop-Viewer": "true",
                "CloudFront-Is-Mobile-Viewer": "false",
                "CloudFront-Is-SmartTV-Viewer": "false",
                "CloudFront-Is-Tablet-Viewer": "false",
                "CloudFront-Viewer-Country": "US",
                "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
                "Upgrade-Insecure-Requests": "1",
                "User-Agent": "Custom User Agent String",
                "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
                "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
                "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
                "X-Forwarded-Port": "443",
                "X-Forwarded-Proto": "https"
            },
            "requestContext": {
                "accountId": "123456789012",
                "resourceId": "123456",
                "stage": "prod",
                "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
                "requestTime": "09/Apr/2015:12:34:56 +0000",
                "requestTimeEpoch": 1428582896000,
                "identity": {
                    "cognitoIdentityPoolId": null,
                    "accountId": null,
                    "cognitoIdentityId": null,
                    "caller": null,
                    "accessKey": null,
                    "sourceIp": "127.0.0.1",
                    "cognitoAuthenticationType": null,
                    "cognitoAuthenticationProvider": null,
                    "userArn": null,
                    "userAgent": "Custom User Agent String",
                    "user": null
                },
                "path": "/prod/hello",
                "resourcePath": "/hello",
                "httpMethod": "GET",
                "apiId": "1234567890",
                "protocol": "HTTP/1.1"
            }
        }
    ]
}

Here nothing relevant to say.

events/sqs_event.json

{
    "records": [
        {
            "Records": [
                {
                    "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
                    "receiptHandle": "MessageReceiptHandle",
                    "body": "My own event payload!",
                    "attributes": {
                        "ApproximateReceiveCount": "1",
                        "SentTimestamp": "1523232000000",
                        "SenderId": "123456789012",
                        "ApproximateFirstReceiveTimestamp": "1523232000001"
                    },
                    "messageAttributes": {},
                    "md5OfBody": "4d1d0024b51659ad8c3725f9ba7e2471",
                    "eventSource": "aws:sqs",
                    "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
                    "awsRegion": "us-east-1"
                }
            ]
        }
    ]
}

The same applies here.

hello-world/main.go

package main

import (
	"context"
	"fmt"

	"httplambda/middlewares"

	"github.com/aws/aws-lambda-go/lambda"
)

func lambdaHandler(ctx context.Context, event middlewares.Event) (string, error) {
	_ = ctx
	fmt.Println("Path:", event.Records[0].APIGatewayProxyRequest.Path)

	fmt.Println("Hi from HTTP-triggered lambda!")

	return "", nil
}

func main() {
	// start the lambda handler
	lambda.Start(middlewares.LogMiddleware(context.Background(), lambdaHandler))
}

Please note how we can access the info on the event.

sqs/main.go

package main

import (
	"context"
	"fmt"

	"httplambda/middlewares"

	"github.com/aws/aws-lambda-go/lambda"
)

func lambdaHandler(ctx context.Context, event middlewares.Event) (string, error) {
	_ = ctx
	fmt.Println("Queue name:", event.Records[0].SQSEvent.Records[0].EventSourceARN)
	fmt.Println("Hi from SQS-triggered lambda!")
	return "", nil
}

func main() {
	lambda.Start(middlewares.LogMiddleware(context.Background(), lambdaHandler))
}

Final

There are a couple of considerations to do:

  1. Before following this solution, I tried to use the type parameters without any luck. It seems that they're not allowed in the middlewares' signatures.
  2. The code is oversimplified and it's not production-ready.

Let me know if this helps or if you need anything else, thanks!

huangapple
  • 本文由 发表于 2023年2月2日 18:00:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75321426.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定