Golang AWS API Gateway неверный символ 'e' ищет начало значения - PullRequest
0 голосов
/ 12 октября 2018

Я пытаюсь создать API-шлюз, подключенный к лямбде, который анализирует HTML-шаблон с помощью руля и затем возвращает его, но я получаю эту ошибку, когда запускаю его локально и даже по URL-адресу теста с использованием AWS.

{
"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, который вы когда-либо видели, но я начинающий, когда дело доходит до Golang, так как я в основном разработчик PHP, но компания, в которой я работаю, создает много лямбд Голанга, поэтому я начализучая его.

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
}

Я попытался найти решение проблемы как можно выше, но не смог найти причину этой странной ошибки, которая не очень полезна.Спасибо, что нашли время и помогли

1 Ответ

0 голосов
/ 12 октября 2018

Благодаря @Anzel, который указал мне правильное направление, я решил взглянуть на request.Body, и кажется, что, добавив */* к двоичным типам носителей API Gateway Binary, теперь даже запрос приходит в кодированном и ли вот, как говорилось в сообщении, первое письмо действительно было 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...