php 작업을 진행하다보면, 다른 도메인 혹은 다른 포트(ex. http://localhost:80 <-----> http://localhost:3000) 간의 데이터 전달이 필요한 경우가 있다.
필자 같은 경우, 채팅 기능 구현을 위해서 Node.js의 Socket.io를 사용하게 되었는데, 채팅창을 새로운 브라우저로 띄우게 되는 경우 포트가 기존에 사용했던 80번이 아닌 3000으로 나오기때문에, 단순한 ajax 통신으로는 서로 다른 포트 간의 데이터 송수신이 불가능하다는 것을 알게 되었다.
(.....처음에 코드 잘못 입력해서 그런줄 알고 거짓말 안하고, 수백번은 쳐봤다....).
그렇다면, Ajax를 포함하고 있는 자바스크립트(Javascript)에서 생겨나는 문제인 것 같다고 판단을 하고, 관련자료를 찾아보았다.
구글링 결과, 자바스크립트는 서로 다른 도메인에 대한 요청을 보안상의 이유로 제한을 하고,
이러한 정책을 SOP(Sam-Origin Policy)라고 한다.
그러면, 이러한 SOP정책으로 왜 통신에 실패하는지 이유를 알아보자.
<출처> https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy
위 그림에서 제일 상단에 표시해 둔 메인 URL을 기준으로 아래 표에 있는 URL을 관찰해보자.
'결과'에 실패 라고 적혀 있는 행의 '이유'를 보게 되면 <프로토콜>, <포트>, <호스트> 가 실패의 이유인 것을 알 수 있다.
즉, 메인 URL 같은 경우, <프로토콜 : http>, < 포트 : 80>, <호스트 : store>를 사용하고 있기 때문에,
이와 다른 URL요소를 가지고 있다면, 통신이 불가능하다는 것이다.
( ****혹시, 위에서 언급한 URL요소를 직접 확인해보고 싶다면, 자바스크립트로 로그를 찍어보자. ****)
<script>
console.log(window.location.protocol);
// ----> 프로토콜 ( 'http' 혹은 'https')
console.log(window.location.port);
// ----> 포트번호
console.log(window.location.host);
// ----> // 호스트( 'http://' 의 바로 뒤에 있는 문자열)
console.log(window.location.origin);
// ----> 프로토콜 + 호스트 (ex. 'http://store' 까지 출력)
</script>
.
.
.
.
아니, 그러면 정책이라는 것으로 묶어놨으니 통신을 할 수가 없는 것인가....? 물론 그렇지 않다.
이러한 상황 즉, 크로스 도메인 이슈(Cross domain issue)를 해결하기 위한 방법으로 JSONP라는 것을 사용하면 된다.
.
.
.
.
일단 JSONP를 사용하기 전,
왜 필자는 이러한 JSONP를 사용할 수 밖에 없는 것 인가에 대해서
간단하게(...?), 그림으로 표현해 보겠다.
먼저 위 그림에 대해서 다시 설명하자면,
(1) 채팅시작 버튼을 누름과 동시에 jquery 클릭이벤트 ----> $.ajax 까지 실행이 된다.
(2) ajax를 통해 '발신자 이름'과, '수신자 이름'을 chat_info.php로 전달하고, 변수로 지정해 둔다.
(3) chat_info.php에서 수신한 발신자 이름과, 수신자 이름이 존재한다면, 쉘(Shell)로 채팅 서버(chat_server.js)를 실행시킨다.
----------------------------------------------------------------------------------------------------------------------------------
이제 (3)에서 채팅서버(chat_server.js)가 실행되는 순간이다.
(3-1) 미리 socket.io 통신용으로 열어둔 3000포트로 채팅 서버와 실제 사용자에게 보여질 채팅 창(chat_client.html)으로 get타입의 데이터를 전송.
(4) chat_server.js가 성공적으로 실행되고 나면, 다시 main.php의 ajax 부분에서 success : function(data)로 타고 들어가서,
(5) chat_server.js에서 3000번 포트와 연결되어있던 채팅 창(chat_client.html)을 실행시킨다.
(URL ---> 'http://localhost:3000' )
EX) **********************************************************
app.get('/test', function(req,res) {
res.sendFile('/chat_client.html');
)};
만약 위와 같이 작성했다면, chat_client.html의
url은 'http://localhost:3000/test'가 된다.
***************************************************************
.
.
.
.
.
자 그러면, 이제 채팅창은 성공적으로 띄웠고, 채팅창을 띄우기 전에 이미 php 파일에 변수로 지정해둔 '발신자 이름'과 '수신자 이름'까지 준비되어 있다.
여기서 당연하게 채팅창은 html 파일이므로, 내부에 javascript를 이용해서 ajax로 '발신자 이름', '수신자 이름'을 담고 있던 php 파일(chat_info.php)로 접근해서 가져오면 될거라고 생각을 했다.
.
.
.
당연히 안된다. 위에서 미리 말했던 SOP정책 때문이다.
.
.
.
chat_info.php
url : 'http://localhost/chat_info.php'
chat_client.html
url : 'http://localhost:3000'
<프로토콜(=http)>과 <호스트(=localhost)> 가 같더라도, 정작 <포트>가 서로 다르기 때문에 ajax로는 통신을 할 수 없게 되는 셈이다.
이제 여기서 JSONP의 효과를 볼 차례이다.
일단 포트 3000번 채팅창에서 80번 포트에 저장된 '발신자 이름', '수신자 이름' 데이터를 가져와야하는 상황이다.
그러면 chat_client.html 내부에서 ajax를 통한 jsonp 통신 코드를 작성해보겠다.
--------------------------------------------------------------------------------
[ chat_client.html ]
$.ajax({
url : 'http://localhost:80/chat_info.php',
dataType: 'jsonp',
jsonp: 'callback',
success : function(data) {
alert(data);
}
});
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
// 테스트로 수신자 이름(receiver_name)만 받아보자.)
[ chat_info.php ]
header("content-type:text/html; charset=utf-8");
$sender_name = $_POST['sender_name'];
$receiver_name = $_POST['receiver_name'];
$callback = $_REQUEST['callback'];
if(!isset($_REQUEST['callback'])) {
unset($_SESSION['receiver']);
session_start();
$_SESSION['receiver'] = $receiver_name;
}
else {
session_start();
echo $callback."(".json_encode($_SESSION['receiver'].")";
}
--------------------------------------------------------------------------------
이렇게 작성하면 정상적으로 chat_client.html 실행 후, alert 창으로 수신자 이름이 뜨는 것을 확인할 수 있다.
jsonp를 사용할 때 서버에서는
echo $callback."(".json_encode( ).")";
형태로 끝나야 정상적으로 전송이 된다.(*** 형식 주의!!!).
+)
필자가 작성한 jsonp 사용법은 ajax를 통해 사용하였고, <script> 호출을 통한 방법으로 ajax 사용없이 javascript로 사용하는 방법도 있다.
실제로 조사해본바, jsonp의 개념 자체가
'HTML의 <script>요소로 요청되어지는 호출은 SOP정책에 위반되지 않는다는 점을 이용한 우회방법'
이라고 나온다.
사실 이 글을 써보기 전에 이미 ajax를 통해 데이터 송수신을 성공해서, jsonp의 원론적인 부분은 알아보지 않았지만 확실히 이해하고 쓰는게 추후에 더 도움이 되지 않을 까 싶다.
하지만, jQuery 쪽에서 이미 jsonp에 대한 기능을 손쉽게 지원해주기 때문에 이러한 방법도 틀린 것은 아니라고 말하고 싶다.
기능 구현이 시급한 쪽이라면 ajax를 통한 jsonp 사용으로 크로스도메인 이슈를 우회하는 것도 괜찮은 방법 같다.