Rad Blog

Archive

자바로 하는 HTTP 요청 사용과 스프링 컨트롤러 어노테이션 정리

2022-04-02 Java Spring xfrnk2

개요

  • 본 글은 넷플릭스 서버를 나름대로 클론 코딩을 해보며 데이터를 채우던 과정중의 기록이다.
  • 외부 API를 사용하면서 어떻게 하면 HTTP 요청을 외부 API로 보내고, 응답을 받아 올 수 있는지 공부해볼 수 있었다.
  • 깊이가 있다고는 말할 수 없지만, 이해하며 기록해 왔던 내용을 아래에 정리해 본다.
  • 물론 세부적인 내용은 대부분 공식문서를 참고했으며, 자주 사용되어 꼭 알아야 했었던 개념 위주로 정리한다.

Body

Body는 보통 key, value의 쌍으로 이루어지기 때문에 자바에서 제공해주는 MultiValueMap 타입을 사용해야한다.

MultiValueMap<String, String> params =new LinkedMultiValueMap<>();
params.add('')

MultiValueMap 타입으로 만들어준 변수에 add()를 사용해 보낼 데이터를 추가해준다.

HTTP POST를 요청할때 보내는 데이터(Body)를 설명해주는 헤더(Header)도 만들어서 같이 보내줘야 한다.

HttpHeaders headers =new HttpHeaders();
headers.add("");

Spring Framework에서 제공해주는 HttpHeaders 클래스는 Header를 만들어준다.

add()를 사용해 Header에 들어갈 내용을 추가해주자.

Body와 Header 결합

HttpEntity<MultiValueMap<String, String>> entity =new HttpEntity<>(params, headers);

Spring Framework에서 제공해주는 HttpEntity 클래스는 Header와 Body를 합쳐준다.

Post 요청

RestTemplate rt =new RestTemplate();

ResponseEntity<String> response = rt.exchange(
                "https://{요청할 서버 주소}",//{요청할 서버 주소}
                HttpMethod.POST,//{요청할 방식}
                entity,// {요청할 때 보낼 데이터}
                String.class {요청시 반환되는 데이터 타입}
);

Spring Framework에서는 서버에 요청을 편하게 하기 위한 RestTemplate 클래스를 제공해준다.

RestTemplate

HTTP 메소드에 의한 평범한 기능 템플릿을 제공해주고, 더 나아가 특별한 케이스를 지원하는 exchange와 execute 메소드를 제공해준다.

exchage() : 모든 HTTP 요청 메소드를 지원하며 원하는 서버에 요청시켜주는 메소드

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

RestTemplate (Spring Framework 5.2.9.RELEASE API) Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, and others. The RestTemplate offers templates for common scenarios by HTTP m docs.spring.io

response에 서버에서 응답해준 데이터가 저장됨

RequestBody와 ResponseBody 차이

  • @RequestBody 어노테이션을 이용하면 HTTP 요청 Body를 자바 객체로 전달받을 수 있다.
  • @ResponseBody을 이용하면 자바 객체를 HTTP 응답 body로 전송할 수 있다.

consumes와 produces 차이

받고싶은 데이터를 강제를 함으로써 오류상황을 줄여서 매핑

consumes는 들어오는 데이터 타입을 정의할때 이용

produces는 그 반대

출처 : [https://mungto.tistory.com/438](https://mungto.tistory.com/438)

JSON Parsing (Simple하다)

링크 참조 : [https://codechacha.com/ko/java-parse-json/](https://codechacha.com/ko/java-parse-json/)

RESPONSE ENTITY의 JSON화

JSONParser jsonParser = new JSONParser(); 
JSONObject jsonObject = (JSONObject) jsonParser.parse(response.getBody().toString()); 
  • Body 부분만 사용하는 경우에 해당
  • 헤더가 필요하면 getBody()는 필요 없음

참고 : [https://a1010100z.tistory.com/5](https://a1010100z.tistory.com/5)

JSON VS GSON

서버에서 API를 통해 JSON 값들을 내려받으면 클라이언트인 안드로이드는 이를 파싱해서 필요한 곳에 알맞게 값들을 넣어주거나 표시해야 한다.

그러나 매번 JSONObject, JSONArray를 일일이 선언해서 파싱할 수는 없다. 귀찮다.

이 귀찮음을 해소하기 위해 존재하는 라이브러리가 바로 Gson이라는 라이브러리다. 이걸 쓰면 귀찮아서 숨 넘어갈 것 같은 JSON 파싱 과정을 매우 많이 단축시켜준다.

주의할 것은 JSON 파싱을 수작업으로 해본 적 없이 이 라이브러리를 먼저 사용하지 않는 것이다. 최소한 내가 받은 JSON이 어떤 구조인지 대충 짐작할 수 있고 로직이 어찌저찌 생각나는 정도가 되고 나서야 사용하면 좋다. 단순한 구조의 JSON이라면 Gson을 쓰는 것보다 수작업으로 파싱 때리는 게 더 나을 수도 있다. [https://github.com/google/gson](https://github.com/google/gson) 한줄설명 : 자바 객체 <-> JSON 변환할 때 사용할 수 있는 자바 라이브러리 출처 : https://onlyfor-me-blog.tistory.com/451


Spring 4.3부터 사용 가능해진 Spring MVC 컨트롤러 메소드를 위한 새 어노테이션

https://jira.spring.io/browse/SPR-13442

  • @PostMapping
  • @GetMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

→ 기존 RequestMapping을 간결하게 사용할 수 있게 도와준다.

→ 어떤 HttpMethods로 매핑시킬지 명확하고 코드 수도 짧아졌다.

예) Before

@RequestMapping(value = "/getList", method = { RequestMethod.POST })

After

@PostMapping("/getList")


Contents-type에 따른 차이

1. @RequestBody Person person

model 객체 앞에 @RequestBody 어노테이션을 붙이면, AnnotationMethodHandlerAdapter에 의해 MappingJacksonHttpMessageConverter가 등록되게 된다. MappingJackson2HttpMessageConverter는 HttpMessageConverter의 구현체로 JSON 형식의 데이터가 들어오면 (당연히 content-type은 application/json) 해당 json 데이터를 jackson 라이브러리를 사용하여 model 객체로 변환해주게 된다.

  • Content-Type : application/json 이고 json 타입의 데이터
@PostMapping(value = "/add")
public String postHanlder(@RequestBody Person person) {
	log.info("person :: {}", person);
	return person.toString();
}

2. @RequestBody MulityValueMap<String, String> map

MultiValueMap 과 함께 @RequestBody 어노테이션을 붙이면, AnnotationMethodHandlerAdapter에 의해 FormHttpMessageConverter가 등록되게 된다. FormHttpMessageConverter는 미디어 타입이 application/x-www-form-urlencodede로 정의된 폼 데이터를 주고 받을 때 사용하게 된다.

  • Content-Type : application/x-www-form-urlencoded 이고 key1=value1 타입의 데이터
@PostMapping(value = "/add")
public String postHanlder(@RequestBody MultiValueMap<String, String> data) {
	log.info("data :: {}", data);
	return data.toString();
}

물론 이론상으로는 2번 방법이 가능하지만, FormHttpMessageConverter를 더 효율적으로 사용하려면 아래 3번 방법이 더 낫다.

3. @ModelAttribute Person person

model 객체와 함께 @ModelAttribute를 사용하면 2번과 같이 FormHttpMessageConverter가 등록되어 key=value를 model로 converting 하게 된다. 앞에 아무런 어노테이션도 안붙이고 Person person 으로 선언하면 @ModelAttribute가 암묵적으로 사용된다.

  • Content-Type : application/x-www-form-urlencoded 이고 key1=value1 타입의 데이터
@PostMapping(value = "/add")
public String postHanlder(Person person) {
	log.info("data :: {}", data);
	return data.toString();
}

리턴 타입 : JSONArray vs JSONObject

예시코드

@GetMapping("/test")
    public ResponseEntity<List> testList(testVo testVo) {
        String url = "http://testurl.com"; // api url

  //get parameter 담아주기
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("test1", testVo.getUserId())
                .queryParam("test2", testVo.getValue());
       HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setConnectTimeout(30000); // 연결시간 초과
        //Rest template setting
        RestTemplate restTpl = new RestTemplate(httpRequestFactory);
        HttpHeaders headers  = new HttpHeaders(); // 담아줄 header
        HttpEntity entity = new HttpEntity<>(headers); // http entity에 header 담아줌

        ResponseEntity<JSONArray>  responseEntity = restTpl.exchange(url, HttpMethod.GET, entity, JSONArray.class);
        L.info("responseEntity.getBody()" + responseEntity.getBody());
        List result = (List) responseEntity.getBody();

        return ResponseEntity.ok(result);
    }

HttpComponentsClientHttpRequestFactory :  RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());이렇게 써도 가능하나 다양한 설정을 추가하지 못한다.

POST 방법으로 하고 싶으면 HttpEntity를 선언할때 body부 를 넣어주면된다.

String body =~~; // 보통 map형식

HttpEntity entity = new HttpEntity(body,headers);

그리고 여기서는 return을 List로 받고싶어서 JSONArray를 썼는데,

List 형태가 아닌 다양한 JSon 형태라면 JSONObject로 리턴받아 해당 값을 get해도 된다.

ResponseEntity<JSONObject>  responseEntity = restTpl.exchange(url, HttpMethod.GET, entity, JSONObject.class);

responseEntity.getBody().get("example");

참조 문서

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html


RequestParam과 RequestBody의 차이

@RequestBody 로 데이터를 받을 때는 메서드의 변수명이 상관이 없었지만, @RequestParam 으로 데이터를 받을때는 데이터를 저장하는 이름으로 메서드의 변수명을 설정 해주어야 한다.

  • RequestBody는 날것의 데이터를 넣지 말고 자바 객체가 맵핑 되는점 주의

만약에…

기존에 json 방식으로 요청을 날리던 유저들에게는 그대로 제공하되 추가적으로 x-www-url-encoded 방식도 제공을 해야하는 상황이다.

@PostMapping(value = “/add”, consumes = MediaType.APPLICATION_JSON_VALUE) public String postHanlderForJsonRequest(@RequestBody Person person) { [log.info](http://log.info/)("» json type :: person :: {}", person); return person.toString(); }

@PostMapping(value = “/add”, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) public String postHanlderForFormRequest(Person person) { log.info("» form type :: person :: {}", person); return person.toString(); } 위와 같이 두개의 method를 만들어주면 된다. 첫번째 method는 json 타입을, 두번째 method는 form-urlencoded 타입을 받게 된다.


HttpServletRequest

HttpServletRequest를 사용하면,  값을 받아올 수 있다.

예를들어, 아이디, 비밀번호 등의 데이터를 컨트롤러로 보냈을때,

HttpServletRequest 객체안에 모든 데이터들이 들어가게 된다.

@Controller
public class HomeController {
    @RequestMapping("/board/confirmId")
    public String confirmId(HttpServletRequest httpServletRequest, Model model) {
        String id = httpServletRequest.getParameter("id");
        String pwd = httpServletRequest.getParameter("pwd");
        model.addAttribute("id", id);
        model.addAttribute("pwd", pwd);
        return "board/confirmId";
    }
}

id와 pwd를 HttpServletRquest 객체를 통해 받아오고, Model 객체를 이용해서 뷰로 값을 넘기고 있다.

comments powered by Disqus