+3

[Go] Lambda - APIGateway Xử Lý Request Response

Mục Tiêu:

  • Nhận request data từ client thông qua api-gateway
  • Response data về cho client thông qua api-gateway
  • Cấu hình đơn giản như: timeout, memory, environment,...

Chuẩn Bị:

  • Để tiếp tục đọc và hiểu những phần sau, cần phải có một lambda function được tạo trước đó.
  • Nếu bạn chưa tạo một lambda function thì tham khảo here để hiểu các phần sau.

Giải Quyết Vấn Đề

1. Nhận request data:

  • http body có dạng.
{
    "request_id":"123123213",
    "data": {
        "name":"open dev"
    }
}
  • Trong lambda muốn get data từ client, thì sử dụng api-gateway events APIGatewayProxyRequest. Struct này là của aws-sdk trong struct chỉ cần quan tâm field Body và dùng json.Unmarshal
// APIGatewayProxyRequest contains data coming from the API Gateway proxy
type APIGatewayProxyRequest struct {
	Resource                        string                        `json:"resource"` // The resource path defined in API Gateway
	Path                            string                        `json:"path"`     // The url path for the caller
	HTTPMethod                      string                        `json:"httpMethod"`
	Headers                         map[string]string             `json:"headers"`
	MultiValueHeaders               map[string][]string           `json:"multiValueHeaders"`
	QueryStringParameters           map[string]string             `json:"queryStringParameters"`
	MultiValueQueryStringParameters map[string][]string           `json:"multiValueQueryStringParameters"`
	PathParameters                  map[string]string             `json:"pathParameters"`
	StageVariables                  map[string]string             `json:"stageVariables"`
	RequestContext                  APIGatewayProxyRequestContext `json:"requestContext"`
	Body                            string                        `json:"body"`
	IsBase64Encoded                 bool                          `json:"isBase64Encoded,omitempty"`
}
  • Khai báo một struct để unmarshal data
type RequestBodyAPIGW struct {
	RequestID string      `json:"request_id"`
	Data      interface{} `json:"data"`
}
  • Unmarshal data:
json.Unmarshal([]byte(eventReq.Body), &req)

2. Response data:

  • client muốn response có format. Trong đó field data thì trả về dữ liệu giống với request truyền vào.
{
    "data": {
        "name": "open dev"
    },
    "responseId": "123123213",
    "responseMessage": "successfully",
    "responseTime": "2022-12-17T03:53:42.325+00:00"
}
  • Để lambda return data, thì cũng sử dụng api-gateway events APIGatewayProxyResponse.
// APIGatewayProxyResponse configures the response to be returned by API Gateway for the request
type APIGatewayProxyResponse struct {
	StatusCode        int                 `json:"statusCode"`
	Headers           map[string]string   `json:"headers"`
	MultiValueHeaders map[string][]string `json:"multiValueHeaders"`
	Body              string              `json:"body"`
	IsBase64Encoded   bool                `json:"isBase64Encoded,omitempty"`
}
  • Thông thường response sẽ có 2 dạng, api xảy ra error hoặc không. Mình viết 2 hàm response và gắn vào field Body.
type HttpResponse struct {
	Uuid string // uuid, indicator per api
	Err  error 
	Time string // time tracing
	Data interface{}
}

func responseOk(respBody HttpResponse) string {
	var buf bytes.Buffer
	mapRes := map[string]interface{}{
		"responseId":      respBody.Uuid,
		"responseMessage": "successfully",
		"responseTime":    respBody.Time,
	}
	if respBody.Data != nil {
		mapRes["data"] = respBody.Data
	}
	body, errMarshal := json.Marshal(mapRes)
	if errMarshal != nil {
		log.Default().Println("marshal response err", errMarshal)
	}
	json.HTMLEscape(&buf, body)
	return buf.String()
}

func responseErr(respBody HttpResponse) string {
	var buf bytes.Buffer
	mapRes := map[string]interface{}{
		"responseId":      respBody.Uuid,
		"responseMessage": respBody.Err.Error(),
		"responseTime":    respBody.Time,
	}

	body, errMarshal := json.Marshal(mapRes)
	if errMarshal != nil {
		log.Default().Println("marshal response err", errMarshal)
	}
	json.HTMLEscape(&buf, body)
	return buf.String()
}

3. Cấu hình timeout, memory, env:

  • Để cấu hình 3 thuộc tính trên, đơn giản chỉ cần mở file serverless.yml.
  • Trong block provider: ta time 2 field timeoutmemorySize, thì đã set được timeout và resource.
provider:
   timeout: 6
   memorySize: 256
  • Environment của lambda, cũng tương tự trong block provider:, thêm environment: trong lambda sẽ có env env_test có giá trị là value-test
provider:
      environment:
            env_test: "value-test"

Deploy Function:

  • file Makefile:
build:
	env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
clean:
	rm -rf ./bin
deploy: clean build
	sls deploy --verbose
  • run:
make deploy
  • output:

Test:

Full source:

  • file golang:
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"log"
	"time"

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

type Response events.APIGatewayProxyResponse

type RequestBodyAPIGW struct {
	RequestID string      `json:"requestId"`
	Data      interface{} `json:"data"`
}

// type ResponseBodyAPIGW struct {
// 	RequestID string `json:"request_id"`
// 	Message   string `json:"message"`
// }

func Handler(ctx context.Context, eventReq events.APIGatewayProxyRequest) (Response, error) {
	var (
		req  = RequestBodyAPIGW{}
		resp = Response{
			StatusCode:      404,
			IsBase64Encoded: false,
			Headers: map[string]string{
				"Content-Type":           "application/json",
				"X-MyCompany-Func-Reply": "hello-handler",
			},
		}
	)
	err := json.Unmarshal([]byte(eventReq.Body), &req)
	if err != nil {
		resp.Body = ParseResponse(HttpResponse{
			Uuid: req.RequestID,
			Err:  err})
		return resp, nil
	}
	resp.StatusCode = 200
	resp.Body = ParseResponse(HttpResponse{Uuid: req.RequestID, Data: req.Data})
	return resp, nil
}

func main() {
	lambda.Start(Handler)
}

type HttpResponse struct {
	Uuid string // uuid, indicator per api
	Err  error
	Time string // time tracing
	Data interface{}
}

func ParseResponse(respBody HttpResponse) string {
	respBody.Time = time.Now().Format("2006-01-02T15:04:05.000-07:00")
	if respBody.Err != nil {
		return responseErr(respBody)
	}
	return responseOk(respBody)
}

func responseOk(respBody HttpResponse) string {
	var buf bytes.Buffer
	mapRes := map[string]interface{}{
		"responseId":      respBody.Uuid,
		"responseMessage": "successfully",
		"responseTime":    respBody.Time,
	}
	if respBody.Data != nil {
		mapRes["data"] = respBody.Data
	}
	body, errMarshal := json.Marshal(mapRes)
	if errMarshal != nil {
		log.Default().Println("marshal response err", errMarshal)
	}
	json.HTMLEscape(&buf, body)
	return buf.String()
}

func responseErr(respBody HttpResponse) string {
	var buf bytes.Buffer
	mapRes := map[string]interface{}{
		"responseId":      respBody.Uuid,
		"responseMessage": respBody.Err.Error(),
		"responseTime":    respBody.Time,
	}

	body, errMarshal := json.Marshal(mapRes)
	if errMarshal != nil {
		log.Default().Println("marshal response err", errMarshal)
	}
	json.HTMLEscape(&buf, body)
	return buf.String()
}
  • file serverless.yml
service: lambda-go
frameworkVersion: '3'

provider:
  name: aws
  runtime: go1.x
  timeout: 6
  memorySize: 256

  environment:
    env_test: "value-test"

package:
  patterns:
    - '!./**'
    - ./bin/**

functions:
  hello:
    handler: bin/hello
    events:
      - httpApi:
          path: /hello
          method: post

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.