본문 바로가기
Cloud/AWS

AWS 게이트웨이+람다에서 HTTPStatusCode 400을 설정해보기

by 크라크라 2023. 11. 5.

순수히 테스트용 또는 단순 작업용으로만 API 를 만드는 것이 아니라면 API의 응답 상태코드는 중요합니다.

회사나 개인, 프로젝트마다 참여하시는 분들의 취향이 달라서 서로 다른 방식을 택할 수는 있겠지만요. 

 

 

일반적으로 크게 두 가지 방법을 사용하는 것 같습니다. 

 

1. 모든 처리를 다 정상적으로 한다는 전제하에서 기본 HTTP 응답코드를 200으로 받고, Body 안에서 실제 응답을 처리하는 방법입니다. 아래의 예시는 그 방법을 보여줍니다. 

HTTPStatusCode : 200 

Body 
{
  "statusCode" : 200,
  "message" : "성공"
}

Body 
{
  "statusCode" : 400,
  "message" : "필수 데이터가 누락되었습니다."
}

Body 
{
  "statusCode" : 500,
  "message" : "서버 오류가 발생하여습니다."
}

 

2. 기본 HTTP 응답코드를 가능하면 그 정의에 맞춰서 받는 방식입니다. 정상이면 200을, Bad Request는 400을 InternalServerError 라면 500을 받고 옵션으로 Body에 statusCode 를 병기하거나, 생략하는 방식을 취할 수 있습니다.

HTTPStatusCode : 200 

Body 
{
  "statusCode" : 200,
  "message" : "성공"
}

HTTPStatusCode : 400

Body 
{
  "statusCode" : 400,
  "message" : "필수 데이터가 누락되었습니다."
}

HTTPStatusCode : 500 

Body 
{
  "statusCode" : 500,
  "message" : "서버 오류가 발생하여습니다."
}

 

 

AWS에서 API Gateway와 Lambda 함수를 연동하게 되면 기본적으로는 1. 의 방식으로 응답을 받습니다. 

AWS에서 제시하는 기본 가이드라인대로 람다함수의 응답을 조절하더라도 실제 Gateway 밖에서는 200 OK 응답을 받는 것이죠. 

 


 

이것을 2. 방식으로 사용하기 위해서는 몇 가지 추가 작업이 필요합니다. 아래에서는 그 방법에 대해서 적어보도록 하겠습니다. 

 

일단 API Gateway와 람다 함수가 이미 연동되어있다고 가정하겠습니다. 

저 같은 경우에는 API 게이트웨이에는 test-node 라는 함수로 nodejs 를 테스트해보고, test-python 이라는 함수에서 python 을 테스트해보겠습니다. 람다함수에는 이에 대응하여 각각의 함수를 지정해주었습니다. (테스트는 편하게 하기 위해서 POST로 만들었습니다.)

 

 

 

그럼 python 부터 한 번 진행해보겠습니다.

 

람다함수는 아주 간단한 형태의 테스트를 하기 위한 것이므로 다음과 같이 작성하였습니다.

import json

def lambda_handler(event, context):
    if event.get("test") == 400:
        return {
        'statusCode': 400,
        'body': json.dumps('[BadRequest] 400 Error!')
        }
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

 

그러면 일반적으로 생각하는 동작 방식은 statusCode 가 400이므로 body 에 test 를 400으로 요청하면 statusCode 가 그 값으로 나와야겠죠?

 

하지만 실제로 실행을 해보면 다음처럼 200 응답을 받습니다. 이상하네요. body의 statusCode 는 400 이지만 실제 API의 status는 200이네요. 

 

저는 이 Status 값을 400으로 받고 싶습니다. 코드를 다음과 같이 바꿔서 예외를 강제로 발생시켜보겠습니다. test에 400을 입력하면 예외가 발생하는 코드로 바꿨습니다.

import json

def lambda_handler(event, context):
    if event.get("test") == 400:
        raise Exception("[BadReqeust] 400 Error!")
        
        return {
        'statusCode': 400,
        'body': json.dumps('[BadReqeust] 400 Error!')
        }
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

 

 API 게이트웨이의 응답은 어떨까요? 

응답은 정상적인 가이드된 구조 statusCode 와 body를 응답하는 구조가 아니라 이제는 errorMessage, errorType, stackTrace 등을 응답하는 구조로 바뀌었네요. 그런데 에러를 응답했음에도 Status는 여전히 200인 것을 볼 수가 있습니다.

 

 


 

 

방법은 다음과 같습니다. 

 (1) Method Response 에서 status code 추가

 (2) Integration Response 에서 해당 status code로 전달되로고 정규식 추가 

 (3) 람다함수 수정 ( raise Exception ) 통해서 맞는 에러 메시지 추가

 

 

 

일단 다시 API 게이트웨이 화면으로 돌아가봅시다. 

 

여기에서 Method Response 를 클릭합니다. 

 

들어가보면 이렇게 Response 200만 정의가 되어있습니다. 여기있는 Method Response가 최종적으로 앱 혹은 클라이언트가 받는 응답을 정의하는 부분입니다. 

 

여기에서 Create Response 버튼을 눌러서 원하는 응답코드를 추가합니다. 저는 400과 500을 추가해보겠습니다.

HTTP Status code 만 값을 입력하고, 다른 값들을 기본값으로 두고 Save 눌러주시면 충분합니다. 별도로 변환이 필요하다면 추가해야겠죠? 

 

 추가를 하고 난 뒤에는 Method Response 화면에서 Response 200, Response 400, Response 500 을 확인할 수 있습니다. 

 

이제 이 상태로 Intergratino Response 로 넘어가봅시다.

 

 Default Response 가 200 으로 설정되어있는 것을 볼 수 있습니다. 

 

HTTP status regex 는 - 로 적혀있고, Method response status code 는 200 으로 설정되어있죠. Default mapping 은 True 이고요. content handling 은 Passthrough 입니다. 

 

 - HTTP status regex 는 정규식으로 errorMessage 를 검토합니다. 특별한 정규식이 설정되어있지 않으므로  전부 해당됩니다.

 - Method response status code 는 이 정규식에 해당될 경우의 응답 스테이터스 코드를 지정합니다. 

 - content handling 은 응답 데이터를 그대로 넘길 것인지 변형해서 넘길 것인지를 지정합니다. 

 - Default mapping 은 기본값 여부를 지정합니다. 

 

 

이제 여기에 Create Response 버튼을 눌러서 정규식을 추가해줍시다. 

여기서 말하는 정규식은 실제로 발생한 문구의 에러메시지를 검토합니다. (왜 이름은 HTTP Status regex 일까요? 이해는 안됩니다)

 

 HTTP status regex 에는 앞서 말했듯이 에러메시지를 검토할 정규식을 작성해줍니다. 

그리고 Method response status code 는 앞서 Method response 화면에서 추가해준 응답코드들이 리스트로 나옵니다. 

즉, Method response 화면에서 먼저 400, 500 을 추가해주지 않으면 더 이상 추가할 수 없는 에러화면을 보게 됩니다. 

 

이제 다시 함수를 테스트해보면 다음과 같이 Status 400 을 받게됩니다.

 

 

 이제는 Node 로도 한 번 해볼까요? 

다른 모든 순서는 동일합니다. Method Response 를 정의하고, Integration Response 에 정규식을 지정합니다. 그리고 람다함수에 들어가서 에러를 맞춰주면 됩니다. 

 

 다만 일반적으로 js 는 json 구조를 명확하게 맞춰서 사용한다는 점을 고려해서 응답을 복잡하게 해보겠습니다.  

 nodejs 로 생성된 람다는 다음과 같이 작성해보겠습니다. 그러면 무조건 에러 응답이 내려오겠죠. 

export const handler = async (event) => {
  let response = {
    status: 200,
    body: undefined,
    headers: {
      "Content-Type": "application/json",
    },
  }

  try {
    // 강제 에러 유발하기
    console.log(variableThatDoesntExist)

    response.body = "This will be skipped"
  } catch (error) {
    response.status = 400
    response.body = error
    throw new Error(JSON.stringify(response))
  }

  //  dynamic response
  return response
};

 

 응답을 볼까요? 파이썬으로 간단하게 문구만 작성했던 때와는 다르게 json 구조를 통째로 넣어줬기 때문에 기존의 정규식 검토가 진행되지 않습니다. 이 경우에는 어떻게 하는 것이 좋을까요? 

 

에러 메시지를 가지고 처리하는 것도 좋겠지만, 명확하게 우리가 정의한 status code 를 사용하는 방식이 좋을 것 같습니다. 

errorMessage 의 데이터만 가지고 작업하도록 처리해보겠습니다. 

 

일단 Integration Response 에서 정규식 검토하는 방식을 "status":400 을 찾도록 변경했습니다.

 

이렇게만 해도 응답은 다음과 같이 status 400 으로 처리되는 것이 확인됩니다. 

 

다만 우리의 응답이 우리가 정의한 형태로 나가는 것이 더 수용가능한 API 일 것입니다. 그러면 최종적인 응답으로 현재는 response body의 errorMessage만을 받는 것이 더 좋겠죠? 

 

그것을 위해서 mapping template 을 설정해줍시다. mapping template 아래의 create template 버튼을 누르고 실제 데이터 중 errorMessage 만을 실제 데이터로 간주하도록 하는 구문을 추가했습니다.

 

 

마지막으로 한 번 더 실행해보면, 이렇게 깔끔하게 우리가 기존에 사용하던 방식대로 응답을 리턴하는 것이 확인됩니다. 

 

 

복잡한 로직을 따라가지 않고 Status 200을 그대로 쓰는 패턴도 좋습니다만,

이렇게 각 Status 를 구분하는 방식을 택해보는 것은 어떨까요? 구성할 때는 어렵더라도 하고 나면 람다함수 내부를 아주 깔끔하게 관리할 수 있어집니다. 

 

위에서 소개한 python은 아주 단순한 방식으로 처리에 중점을 두었다면, nodejs 기준 방식은 조금 더 명확한 기준으로 성공 응답과 에러 응답을 일치시키는 방법을 소개해드렸습니다. 편한 방식으로 사용해보시기 바랍니다. 

댓글