본문 바로가기
스프링

[스프링] @PostMapping 의 속성 알아보기 (headers, produces)

by kdohyeon (김대니) 2023. 2. 27.
반응형

회사에서 코드리뷰를 하다가 @PostMapping 의 파라미터로 headers 와 produces 가 포함된 코드를 확인한 적이 있습니다. 얼핏 알기로는 headers 는 전달받는 headers 에 대한 정보를 담고 있고, produces 는 응답값의 타입을 정의하기 위해 사용한다고 알고 있습니다. 정확하지가 않아 이번 기회에 한번 알아보고자 합니다.


HTTP 메시지 구조

@PostMapping 은 HTTP 통신에 있어 필요한 어노테이션이기 때문에 먼저 HTTP 메시지 구조에 대해 알아보자.

HTTP 메시지는 클라이언트와 서버 사이에서 데이터가 교환되는 방식을 의미하며, 요청 (Request) 과 응답 (Response) 의 두 가지 유형이 존재한다.

요청과 응답은 Start line, Http headers, body 라는 3개의 정보로 하나의 메시지를 구성한다. Start line 에는 HTTP method (GET, POST, PUT, DELETE), URL 등의 정보가 포함되어 있다. HTTP headers 에는 Host, 요청 데이터 타입 등이 포함되어 있다. Body 에는 요청 데이터가 포함되어 있다. @PostMapping 어노테이션도 이런 정보를 컨트롤하기 위해 사용된다. POST 요청 중 특정 URL 에 해당되는 요청을 받는 것으로 정의하고, headers, produces 등의 속성으로 요청을 제한하거나 허용한다.

https://hahahoho5915.tistory.com/62

참고로 위 구조처럼 똑같이 노출되지는 않지만 브라우저의 Network 탭에서도 확인할 수 있다.

참고
들어가기에 앞서, headers, produces 속성은 @GetMapping, @RequestMapping 등에서도 사용되는 속성으로 @PostMapping 에만 국한되는 것은 아닙니다. 뿐만 아니라 consumes, params, path 등 다른 속성도 존재합니다.

@PostMapping - headers

The headers of the mapped request, narrowing the primary mapping.

공식 문서에 따르면 headers 속성은 요청에 대한 headers 정보를 제한한다. headers 에는 1개 이상의 헤더 정보가 포함될 수 있다. 아래 예시에서는 headers 에 Content-Type 을 명시했는데, Content-Type 은 클라이언트와 서버가 주고 받는 데이터의 형식이다. 해당 값이 "application/json" 이라고 명시함으로써 주고 받는 데이터를 json 형식으로 제한한다. 참고로 headers 에는 Content-Type 뿐만 아니라 사용자 언어를 명시하는 Content-Language, 압축 방식을 명시하는 Content-Encoding 등이 존재한다.

예제 코드는 다음과 같다. POST API 로 url 은 /api/v1/blogs 를 가지며 헤더에는 Content-Type 을 "application/json" 으로 명시한다.

@PostMapping(value = "/api/v1/blogs", headers = {"Content-Type=application/json"} )
public ResultResponse<String> createBlog() {
    return ResultResponse.ok("Hello world");
}

이에 대한 테스트 케이스는 다음과 같이 작성할 수 있다.

@ExtendWith(MockitoExtension.class)
class SampleTest {
    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup().build();
    }

    @Test
    void test() throws Exception {
        mockMvc.perform(
                        MockMvcRequestBuilders
                                .post("/api/v1/blogs")
                                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                ).andExpect(status().isOk())
        ;
    }
}

실행을 시켜보면 아래처럼 테스트가 성공한다 (좌). 만약 테스트 코드에서 Content-Type 을 명시하지 않거나 다른 Content-Type 을 명시하면 테스트는 실패하게 된다 (우).

@PostMapping - produces

Narrows the primary mapping by media types that can be produced by the mapped handler. Consists of one or more media types one of which must be chosen via content negotiation against the "acceptable" media types of the request.

공식 문서에 따르면 produces 속성은 응답의 media type 을 제한한다. 1개 이상의 media type 이 포함될 수 있다. 만약 다음과 같이 설정되어 있다면 HTTP 응답 헤더의 Content-Type 을 "application/json" 으로 반환하는 것이다.

@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)

테스트 코드는 다음과 같이 작성할 수 있다.

@ExtendWith(MockitoExtension.class)
class SampleTest {
    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup().build();
    }

    @Test
    void test() throws Exception {
        mockMvc.perform(
                        MockMvcRequestBuilders
                                .post("/api/v1/blogs")
                                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                ).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON)) // <-- produces 에 대한 테스트 정의
        ;
    }
}

@RequestBody 어노테이션과 함께 사용되었을 때

@RequestBody 어노테이션을 사용하면 데이터를 application/json 형태로 전달받는다는 의미이기 때문에 굳이 컨트롤러의 headers 속성으로 Content-Type 을 application/json 으로 명시할 필요는 없다. API 를 호출하는 쪽에서는 무조건 포함시켜 전달해야 한다.

헤더 정보 전달받기

관련 내용에 대해 찾아보다가 헤더 정보를 컨트롤러에서 직접 받아서 사용할 수 있다는 점을 알게 되었다. @RequestHeader 라는 어노테이션을 활용하면 되고, 이 어노테이션을 사용하면 headers 정보가 꼭 포함되어 전달되어야 한다. @RequestHeader 어노테이션을 사용해두었는데, headers 를 전달받지 못한다면 400 Bad Request 가 발생한다.

@PostMapping(value = "/api/v1/blogs")
public ResultResponse<String> createBlog(
    @RequestHeader(name = "Content-Type") String contentType) {
        return ResultResponse.ok(contentType);
}

이렇게 명시할 수도 있고 @RequestHeader Map<String, String> headers 형태로 전체를 전달받을 수도 있다. 이 때는 꼭 headers 가 없어도 되며, 전달받지 못할 경우에는 Map 이 비어있게 된다.

headers 를 전달받지 못했을 때 (좌), 전달받았을 때 (우)

결론

다시 이 글을 작성하게 된 이유로 돌아가보자면, headers 속성으로 "Content-Type=application/json" 을 명시했다. 하지만 @RequestBody 를 사용했기 때문에 명시를 하지 않아도 정상 작동할 것이다. 또한 produces="application/json" 으로 명시되어 있는데, Response 를 반환하는데 null 으로 정의해서 반환한다 (아래 사진에는 나와있지 않지만). 따라서 produces 속성도 굳이 사용하지 않아도 되는 속성이다.

참고 자료

반응형

댓글