我试图创建一个连接到lambda的API网关,该API解析带有手把的HTML模板,然后返回它,但是当我在本地甚至使用AWS在测试url上运行它时,都会遇到此错误。
{
"errorMessage": "invalid character 'e' looking for beginning of value",
"errorType": "SyntaxError"
}
这是我的SAM模板
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: data-template-renderer
Parameters:
Stage:
Type: String
AllowedValues:
- dev
- staging
- production
Description: environment values
Resources:
# Defining the API Resource here means we can define the stage name rather than
# always being forced to have staging/prod. Also means we can add a custom domain with
# to the API gateway within this template if needed. Unfortunately there is a side effect
# where it creates a stage named "Stage". This is a known bug and the issue can be
# found at https://github.com/awslabs/serverless-application-model/issues/191
DataTemplateRendererApi:
Type: AWS::Serverless::Api
Properties:
Name: !Sub "${Stage}-data-template-renderer"
StageName: !Ref Stage
DefinitionBody:
swagger: "2.0"
basePath: !Sub "/${Stage}"
info:
title: !Sub "${Stage}-data-template-renderer"
version: "1.0"
consumes:
- application/json
produces:
- application/json
- text/plain
- application/pdf
paths:
/render:
post:
x-amazon-apigateway-integration:
uri:
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RenderTemplate.Arn}/invocations"
httpMethod: POST
type: aws_proxy
x-amazon-apigateway-binary-media-types:
- "*/*"
RenderTemplate:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
ENV: !Ref Stage
Runtime: go1.x
CodeUri: build
Handler: render_template
FunctionName: !Sub 'render_template-${Stage}'
Timeout: 30
Tracing: Active
Events:
RenderTemplateEndpoint:
Type: Api
Properties:
RestApiId: !Ref DataTemplateRendererApi
Path: /render
Method: POST
Policies:
- !Ref S3AccessPolicy
- CloudWatchPutMetricPolicy: {}
S3AccessPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: data-template-renderer-s3-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: s3:GetObject
Resource: !Sub "arn:aws:s3:::*"
- Effect: Allow
Action: s3:PutObject
Resource: !Sub "arn:aws:s3:::*"
以下是我用于Lambda的代码。请原谅它可能不是您所见过的最好的Golang代码,但我是Go语言的初学者,因为我主要是PHP开发人员,但是我工作的公司正在创建很多Golang Lambda,所以我开始学习它。
package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/aymerick/raymond"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
var sess *session.Session
func init() {
// Setup AWS S3 Session (build once use every function)
sess = session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
}))
}
func main() {
lambda.Start(handleRequest)
}
type TemplateRendererRequest struct {
Template string `json:"template"`
Bucket string `json:"bucket"`
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
}
type EmailResponse struct {
Email string `json:"email"`
}
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
err := json.Unmarshal([]byte(request.Body), &requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
// Get the template object from S3
result, err := s3.New(sess).GetObject(&s3.GetObjectInput{
Bucket: aws.String(requestData.Bucket),
Key: aws.String(requestData.Template),
})
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
defer result.Body.Close()
// The S3 Object Body is of type io.ReadCloser
// below we create a bytes buffer and then convert it to a string so we can use it later
buf := new(bytes.Buffer)
buf.ReadFrom(result.Body)
templateString := buf.String()
// Parse template through Handlebars library
parsedTemplate, err := parseTemplate(templateString, requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
switch requestData.Type {
case "pdf":
return handlePDFRequest(parsedTemplate, requestData)
default:
return handleEmailRequest(parsedTemplate)
}
}
func handleEmailRequest(parsedTemplate string) (events.APIGatewayProxyResponse, error) {
// Put email parsed template in a struct to return in the form of JSON
emailResponse := EmailResponse{
Email: parsedTemplate,
}
// JSON encode the emailResponse
response, err := JSONMarshal(emailResponse)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
return events.APIGatewayProxyResponse{
Body: string(response),
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
func parseTemplate(templateString string, request TemplateRendererRequest) (string, error) {
result, err := raymond.Render(templateString, request.Data)
return result, err
}
func handlePDFRequest(parsedTemplate string, requestData TemplateRendererRequest) (events.APIGatewayProxyResponse, error) {
pdfBytes, err := GeneratePDF(parsedTemplate)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
keyNameParts := strings.Split(requestData.Template, "/")
keyName := keyNameParts[len(keyNameParts)-1]
pdfName := fmt.Sprintf("%s_%s.pdf", keyName, time.Now().UTC().Format("20060102150405"))
_, err = s3.New(sess).PutObject(&s3.PutObjectInput{
Bucket: aws.String(requestData.Bucket),
Key: aws.String(pdfName),
Body: bytes.NewReader(pdfBytes),
})
b64Pdf := base64.StdEncoding.EncodeToString(pdfBytes)
return events.APIGatewayProxyResponse{
Body: b64Pdf,
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/pdf",
},
IsBase64Encoded: true,
}, nil
}
func GeneratePDF(templateString string) ([]byte, error) {
pdfg, err := wkhtmltopdf.NewPDFGenerator()
if err != nil {
return nil, err
}
// Pass S3 Object body (as reader io.Reader) directly into wkhtmltopdf
pdfg.AddPage(wkhtmltopdf.NewPageReader(strings.NewReader(templateString)))
// Create PDF document in internal buffer
if err := pdfg.Create(); err != nil {
return nil, err
}
// Return PDF as bytes array
return pdfg.Bytes(), nil
}
// https://stackoverflow.com/questions/28595664/how-to-stop-json-marshal-from-escaping-and
func JSONMarshal(t interface{}) ([]byte, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
err := encoder.Encode(t)
return buffer.Bytes(), err
}
我试图在高处和低处寻找解决方案,但找不到为什么我会收到这个非常奇怪的错误,这不是很有帮助。 感谢您抽出宝贵时间并提供帮助
答案 0 :(得分:1)
由于@Anzel向我指出了正确的方向,所以我决定查看request.Body
,似乎通过将*/*
添加到API Gateway Binary媒体类型中,现在即使请求也来了用编码和lo看到,第一个字母确实是e
,就像消息中所说的那样。
所以我改变了这个:
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
err := json.Unmarshal([]byte(request.Body), &requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
对此:
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
b64String, _ := base64.StdEncoding.DecodeString(request.Body)
rawIn := json.RawMessage(b64String)
bodyBytes, err := rawIn.MarshalJSON()
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
jsonMarshalErr := json.Unmarshal(bodyBytes, &requestData)
if jsonMarshalErr != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", jsonMarshalErr.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, jsonMarshalErr
}
根据我在网上看到的内容,您还可以更改此内容:
rawIn := json.RawMessage(b64String)
bodyBytes, err := rawIn.MarshalJSON()
对此:
[]byte(b64String)
当我现在进行CURL请求并将其输出到文件时,可以正确获取PDF。