안녕하세요.
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); } }
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이 그나마 낫습니다...)