안녕하세요. 

Content-Type:appliction/json POST 요청에 대한 질문입니다.

JQuery $.ajax 를 이용하여 요청 시에는 정상적으로 동작하나, REST Client(PostMan 등...)의

프로그램으로 요청 시 정상적으로 처리가 안되는 것 같네요.

 

JQuery $.ajax 요청 시

POST http://localhost/ HTTP/1.1
Content-Type: application/json
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://localhost/index.php?module=test&act=dispTestSimulatorAdmin
Accept-Language: ko
Accept-Encoding: gzip, deflate

Host: localhost
Connection: Keep-Alive
Pragma: no-cache
...

module=test&act=procTestAuth&id=1

 

REST Client 프로그램으로 요청 시

 

POST http://localhost/index.php HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: no-cache
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
...

{"module":"test","act":"procTestAuth","id":"1"}

 

디버깅 해보니 $GLOBALS['HTTP_RAW_POST_DATA'] 값이 위와 같이

JQuery $.ajax 로 요청 시는 QueryString 형태로, REST Client 프로그램으로 요청시엔 JSON String으로 넘어옵니다.

Context.class.php 클래스의 _setJSONRequestArgument 함수를 보면 parse_str 로만 처리하고 있는데요.

JSON 문자열 자체를 Parsing 하는 json_decode 로 처리하지 않은 이유가 있을까요?

 

function _setJSONRequestArgument()
{
  if($this->getRequestMethod() != 'JSON')
  {
   return;
  }
  $params = array();
  parse_str($GLOBALS['HTTP_RAW_POST_DATA'], $params);
  foreach($params as $key => $val)
  {
     $this->set($key, $this->_filterRequestVar($key, $val, 1), TRUE);
  }
}

 

  • Lv37

    XE에서 JSON으로 POST하는 기능 자체가 좀 어설픕니다.

     

    Content-Type: application/json 헤더를 넣었다면 당연히 두번째 예제처럼 JSON으로 인코딩된 데이터를 전송해야겠죠. 그런데 XE에서는 헤더는 헤더대로 넣고 정작 내용은 첫번째 예제처럼 쿼리스트링 포맷을 쓰고 있어요. 아마 PHP에 json_decode 함수가 존재하지 않던 시절에 만들어진 기능이라 그렇겠거니 합니다. (반환되는 데이터도 json_encode가 아니라 자체 함수를 사용하기 때문에 배열을 표시하는 방식 등이 조금씩 다릅니다.)

     

    원래 Content-Type 헤더는 클라이언트가 제출하는 데이터의 포맷("이거 JSON임. 잘 해석하삼")을 가리키는 것입니다. 서버에서 반환할 데이터의 포맷("JSON을 반환해 줘")은 Accept 헤더에 넣어야 하지요. 그런데 XE에서는 Content-Type 헤더를 Accept 헤더와 같은 용도로 사용하고 있습니다.

     

    이렇게 하면 PHP단에서 POST 데이터를 자동으로 해석하여 $_POST와 $_REQUEST에 넣어주지 못하는 부작용이 생깁니다. PHP는 Content-Type 헤더가 application/x-www-form-urlencoded 또는 multipart/form-data 둘 중 하나가 아니면 아예 해석을 시도하지도 않거든요. 결국 XE 내부적으로 HTTP_RAW_POST_DATA를 파싱해야 하는 이상한 상황이 벌어지고, PHP 5.6에서 이 변수가 php://input으로 바뀌는 바람에 상당한 혼란을 겪기도 했습니다.

     

    가장 쉬운 해결책은 그냥 Content-Type 헤더를 빼거나 application/x-www-form-urlencoded로 하시고, 일반 쿼리스트링 포맷으로 전송하시면 됩니다. 굳이 JSON으로 요청하지 않더라도 직접 개발하시는 모듈이라면 Context::setResponseMethod() 함수를 사용해서 반환 포맷을 JSON으로 강제 지정할 수 있으니까요.

     

    라이믹스에서는 모든 AJAX 폼 제출에 일반 쿼리스트링 포맷을 사용하고, 결과를 JSON 또는 XML로 돌려받고 싶은 경우 Accept 헤더나 별도의 파라미터를 사용하도록 하고 있습니다. (사실 XML은 더 괴랄합니다. 안 쓰는 게 답이예요. JSON이 그나마 낫습니다...)

  • Lv37
    네.. 기진곰님. 매번 친절한 답변 감사합니다^^
    Core 쪽 코드를 수정하는거라 Core 업데이트시마다 신경을 써줘야 하는
    불편함이 있지만 아래 코드를 추가해서 처리해야겠네요.

    $json = json_decode($GLOBALS['HTTP_RAW_POST_DATA']);
    foreach($json as $key => $val)
    {
    $this->set($key, $this->_filterRequestVar($key, $val, 1), TRUE);
    }