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.
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.
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.
router.Route("POST", "/uploadsessions", session.UploadSessionsLambda)hello humangoodbye humanPOSTMAN to test:

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
}