해당 소스에 중요한? 변화가 있다.

조건

  • content-type : x-www-form-...  
  • method: post
  • 호출을 받는쪽에서는 (스프링부트 3.x) @RequestBody ...
  • url : xxxxx?a=b
  • body : par1=val1..... 
  • 즉 쿼리스트링이나 body 는 전통적인 parameter 형태(k:v)

 

위의 상황에서 empty body 가 나옴.

 

내용 설명

x-www-form.. 은 전통적인 Parameter 형태이다. 즉 키:값의 구조이다. (method 를 논하는게 아니다) 쿼리스트링에 달려오는 파라미터또한 k:v 이다. 

x-www-form 의 경우 request.getparam... 을 하게 되면 쿼리스트링말고도 바디까지 다 읽는다. 이건 j2ee(지금은 자카르타) 의 규약이다. 


"The parameters data from the GET request query string and the POST request body are aggregated into the request parameter set."

(해석): "GET 요청의 쿼리 스트링 데이터와 POST 요청의 바디 데이터는 요청 파라미터 집합으로 '합쳐(집계)'진다."

제미나이가 찾아줌.

 

자.. 그럼 앞의 조건을 보자.

x-www-form... 형태로 던지면서 쿼리스트링에 파라미터가 달려 있다면... req.getPara.... 을 할때 바디까지다 읽어버린다(from stream.. inputstream) 

우리는 한가지 기억할게 Inputstream 을 읽어버리면 1회성이므로 사라진다. 

다행인건 이렇게 읽은거는 파싱해서(content-type 에 맞게) parameters 에 보관한다. 

 

여기까지는 좋았다. inputstream 이 소비되었어도 Parameters 에 있으니까 그걸 꺼내면되거든. 근데 문제는... 스프링쪽의 소스가 변겨오디었다.

 

2.7 까지는 request 에서 읽어버린 parameters 를 잘 돌려줬다면, 3.0에서는 쿼리스트링이 있는경우 이미 소비된 inputstream 을 리턴하게 된다. 아래 소스를 보자. 왼쪽은 3.x 오른쪽은 2.x 에서 사용하는 스프링리소스이다. (스프링부트와 스프링은 별개다)

 

즉 3.x 로 오면서 스프링에서는 쿼리스트링이 없는 경우 기존에 파싱해놓은 parameters 를 그대로 리턴해주지만, 있다면? 소비되었을지도 모를 inputstream 을 그대로 돌려준다. 즉 request.getparam... 을 호출한 적이 있다면 Inputstream 은 이미 소비되어서 비어있으므로 empty 에러를 맞게 된다. 

 

2.7 에서는 무조건 parameters 를 돌려준다.

 

 

그럼 왜 이렇게 바꾼걸까? 제미나이의 변호?? 는

 

왜 스프링은 코드를 이렇게 바꿨을까? (변호)

스프링 개발자들도 이유 없이 바꾼 건 아닙니다. 2.7 방식(무조건 복구)에는 치명적인 논리적 결함이 있었기 때문입니다.

  • 2.7의 문제:
    • 요청: POST /test?mode=debug (Body: name=spring)
    • 2.7 동작: 파라미터 맵을 다 합쳐서 바디를 만듭니다.
    • 복구된 바디: mode=debug&name=spring
    • 왜곡: 원래 바디에는 name=spring만 있었는데, URL에 있던 mode=debug가 마치 바디 데이터인 것처럼 둔갑해서 @RequestBody에 들어갑니다. 이는 데이터 오염입니다.
  • 3.0의 결정:
    • "쿼리스트링이 있으면, 파라미터 맵에서 어디까지가 바디고 어디까지가 쿼리인지 완벽하게 분리할 수 없다."
    • "부정확하게 섞인 바디를 주느니, 차라리 원본 스트림을 주는 게 맞다(Correctness)."

공식 문서 대신 "Javadoc"과 "Github"이 증거

스프링 팀은 이런 디테일한 변경을 문서보다는 **코드 주석(Javadoc)**이나 PR(Pull Request) 코멘트에 남깁니다.

실제로 ServletServerHttpRequest 클래스의 과거 히스토리나 관련 이슈를 뒤져보면, **"파라미터 기반의 바디 재구성은 신뢰할 수 없다(unreliable)"**라는 뉘앙스의 토론이 존재합니다.

특히 **Spring Framework 6 (Boot 3.0)**은 "Jakarta EE 스펙을 엄격하게 준수한다"는 대원칙 하에, 과거에 편의를 위해 제공하던 **"매직(Magic) 코드"**들을 대거 제거했습니다. 2.7의 그 코드는 사실상 **"개발자가 실수로 바디를 날려먹었을 때를 대비한 안전망(Hack)"**이었는데, 3.0에서는 **"정확성을 위해 그 안전망을 걷어낸 것"**입니다.

 

 

 

라고 설명하고 있다. 틀린 말은 아니다. 여하튼 이젠 strictly 하게 처리해야한다

그냥 거칠게 표현하면 이거다.

x-www-form... 의 콘텐츠타입인가?
그럼 RequestParam 으로 받아라. 파라미터니까! 기존에 되던건 사실 봐준거야... 

 

이거다. 


흐음... 요즘은.. 서비스가 하나의 형태로 나타나지 않는다. 똑같은 데이터를 가지고 이런서비스 저런 서비스에서 똑같이 사용할 수 있단 말이지.
방법이야 게이트단에서 각 단에 맞게 보내주면되긴하다. 

스프링에서도 제공하는기술이 데이터 를 XML이나 JSON 형태로 주고 받을 수 있다. 그거지. 많은 추가 작업없이 가능하다.
예로 .htm 으로 호출하면 html이 오고 .json 으로 호출하면 그 결과가 json으로 오면된다. 주로 키맵의 리턴이 되는 형태 (여러 서비스들간의 통신에서?)
로 주고 받을 수 있다는거다.

@RequestBody 와 @ResponseBody 어노테이션은 각각 HTTP 요청 몸체를 자바 객체로 변환하고 자바 객체를 HTTP 응답 몸체로 변환해주는데 사용된다. 

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

@RequestMapping( method = RequestMethod.POST)
@ResponseBody
public String simpleTest(@RequestBody String body) {
return body;
}

@RequestBody 어노테이션은 @RequestMapping에 의해 POST 방식으로 전송된 HTTP 요청 데이터를 String 타입의 body 파라미터로 전달한다.
@ResponseBody 어노테이션은 @RequestMapping 메서드에 적용되면 해당 메소드의 리턴값을 HTTP 응답데이터로 사용한다.

위의 경우 리턴값이 String 이므로 String 데이터를 HTTP 응답 데이터로 전송한다.

응답데이터.. 결과는 아래처럼 간다...
만약 폼에 값들이 들어있었다면

name=John&age=22

위의 경우 요청 몸체 데이터를 body 파라미터에 전달받고 있으며, body 파라미터를 그대로 결과값으로 리턴한다.
그런데 @ResponseBody 어노테이션이 적용되어 있어서 .. HTTP 요청데이터가 그대로 응답데이터로 간다.

해서 html... 웹브라우저 뷰에 

name=John&age=22

나올것이다.

이런것을.. 스프링MVC는 HttpMessageConverter를 이용해서 자바객체와 HTTP 요청/응답 몸체 사이의 변환을 처리한다.



HttpMessageConverter를 이용한 처리

AnnotationMethodHandlerAdapter 클래스는 @RequestBody 어노테이션이 적용된 파라미터나 @ResponseBody 어노테이션이 적용된 메소드에 대해 HttpMessageConverter 를 사용해서 변환처리한다. (정확히는 HttpMessageConveter의 구현클래스를 사용)

이 HttpMessageConverter를 구현한 몇가지 클래스들이 있다.
ByteArrayHttpMessageConveter 등.. 찾아봐라

만약 다른 messageConverter를 사용하겠다면 명시적 정의 가가능하다. (아래처럼...)
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="cacheSeconds" value="0" />
<property name="alwaysUseFullPath" value="true" />
<property name="webBindingInitializer">
<bean class="madvirus.spring.chap06.binder.CustomWebBindingInitializer" />
</property>
<property name="messageConverters">
<list>
<ref bean="byteArrayHttpMessageConverter" />
<ref bean="stringHttpMessageConverter" />
<ref bean="formHttpMessageConverter" />
<ref bean="sourceHttpMessageConverter" />
<ref bean="marshallingHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
</list>
</property>
</bean>
























 



'IT > 스프링' 카테고리의 다른 글

스프링이 객체를 생성하는 형태?  (0) 2012.02.22
뷰 영역???  (0) 2012.01.26
WebApplicationContext 직접접근?  (0) 2012.01.19
캐시옵션 설정  (0) 2012.01.19
HandlerInterceptor  (0) 2012.01.19

+ Recent posts