1

I have a GoLang API behind API Gateway running on AWS Lambda. I'd like to create a new endpoint to handle multiple file uploads. I would like that endpoint to receive and parse those files before returning a result based on the file contents.

1 Answer 1

0

This took me a few hours to figure out so I'd love to share my solution with everyone. AWS Lambda uses Handlers which have a specific format. That format is:

func handler(req events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {

So all of our logic must be contained within a function with this method signature.

  1. Update your router and add a new endpoint to upload to: router.Route("POST", "/uploadsessions", session.UploadSessionsLambda)
  2. Create a file 'hello.txt' - contents hello human
  3. Create a file 'goodbye.txt' - contents goodbye human
  4. Create a new request in POSTMAN to test: required postman setup
  5. Implement the API Gateway / AWS Lambda Proxy function handler func UploadSessionsLambda(_ context.Context, lambdaReq events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {. Below is my implementation. It still needs some tweaking but works as-is. I'll be parsing CSVs in which case you substitute bufio.NewReader with csv.NewReader in the below code.
const uploadLimitBytes = 50000000 // 50 megabytes type UploadResponse struct { Concat string } func UploadSessionsLambda(_ context.Context, lambdaReq events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { contentType := lambdaReq.Headers["Content-Type"] if contentType == "" { return HandleHTTPError(http.StatusBadRequest, fmt.Errorf("request contained no Content-Type header")) } _, params, err := mime.ParseMediaType(contentType) if err != nil { return HandleHTTPError(http.StatusBadRequest, err) } boundary := params["boundary"] if boundary == "" { return HandleHTTPError(http.StatusBadRequest, fmt.Errorf("request contained no boundary value to parse from Content-Type headers")) } stringReader := strings.NewReader(lambdaReq.Body) multipartReader := multipart.NewReader(stringReader, boundary) form, err := multipartReader.ReadForm(uploadLimitBytes) if err != nil { return HandleHTTPError(http.StatusBadRequest, err) } var sb strings.Builder for currentFileName := range form.File { // anonymous file handler func allows for calling defer .Close() httpStatus, handlerErr := func(fileName string) (int, error) { currentFileHeader := form.File[currentFileName][0] currentFile, openErr := currentFileHeader.Open() if openErr != nil { return http.StatusInternalServerError, openErr } defer currentFile.Close() // figure out how to trap this error bufferedReader := bufio.NewReader(currentFile) for { line, _, readLineErr := bufferedReader.ReadLine() if readLineErr == io.EOF { break } sb.Write(line) } return http.StatusOK, nil }(currentFileName) if handlerErr != nil { return HandleHTTPError(httpStatus, handlerErr) } } return MarshalSuccess(&UploadResponse{Concat: sb.String()}) } 

Helper functions:

func HandleHTTPError(httpStatus int, err error) (events.APIGatewayProxyResponse, error) { httpErr := HTTPError{ Status: httpStatus, Message: err.Error(), } if httpErr.Status >= 500 && !ExposeServerErrors { httpErr.Message = http.StatusText(httpErr.Status) } return MarshalResponse(httpErr.Status, nil, httpErr) } func MarshalSuccess(data interface{}) (events.APIGatewayProxyResponse, error) { return MarshalResponse(http.StatusOK, nil, data) } // MarshalResponse generated an events.APIGatewayProxyResponse object that can // be directly returned via the lambda's handler function. It receives an HTTP // status code for the response, a map of HTTP headers (can be empty or nil), // and a value (probably a struct) representing the response body. This value // will be marshaled to JSON (currently without base 64 encoding). func MarshalResponse(httpStatus int, headers map[string]string, data interface{}) ( events.APIGatewayProxyResponse, error, ) { b, err := json.Marshal(data) if err != nil { httpStatus = http.StatusInternalServerError b = []byte(`{"code":500,"message":"the server has encountered an unexpected error"}`) } if headers == nil { headers = make(map[string]string) } return events.APIGatewayProxyResponse{ StatusCode: httpStatus, IsBase64Encoded: false, Headers: headers, Body: string(b), }, nil } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.