https에서 http와 ajax 통신시 proxy 프로그램 만들기 정보
PHP https에서 http와 ajax 통신시 proxy 프로그램 만들기본문
QA 게시판에서 제목과 같은 질문이 나와서 생각난김에 정리 해서 올립니다.
위 그림을 보시면 https://aaa.com 에서 javascript ajax로 http://bbb.com의 api를 호출하려고 합니다.
두 서버간 프로토콜이 다르므로 브라우저에서 보안상 통신을 허용하지 않게됩니다.
혹은 두 프로토콜이 https로 통일되더라도 bbb.com에 CORS(크로스도메인설정) 가 설정되지 않았다면 aaa.com에서는 ajax로 통신할 방법이 없죠.
물론 프론트 기반에서 얘기 입니다. 서버 프로그램간 얘기는 아니구요.
즉 프론트 javascript에서 다른 서버의 api를 호출할때로 선 조건을 제시해야 겠군요.
간혹 저도 경험을 해봤는데 bbb.com에 기술적 협조요청이되지 않는 경우가 있습니다. 지금 상황에서는 bbb.com에 ssl을 설정하고 CORS도 설정하면되지만 제 경험에서는 bbb.com에서 콘텐츠는 줄께 니 알아서 해라~~라고 그냥 사뿐히 무시하는 경우를 몇번 겪었습니다.
서버 프로그램에서는 그냥 사뿐히 가져오지만 문제는 프론트딴에선 못가져오죠. 여기 많은 분들이 full 스택을 다루시겠지만 많은 회사에서는 엄연히 프론트 개발 영역과 서버 개발 영역을 분리하고 각 개발자의 서버 접근 권한이 제한 됩니다. 즉 내가 프론트 개발자 인데 서버 코딩해서 뷰를 뿌려 줄래~~! 이건 허용되지 않는 문제죠. 이때는 정중히 서버 개발자와 업무 협조 요청을 해야합니다.
우회기법으로 내 서버내에 proxy 프로그램을 개발하고 그 proxy와 bbb.com과 통신을 시도할 수 있습니다.
위 그림처럼 client.php는 ajax로 proxy.php과 통신합니다. proxy.php는 bbb.com의 api.php php 라이브러리인 curl로 통신하는 구조이죠.
여하튼
client.php 소스는 대충 임의로 짰습니다.
<?php
include_once('./common.php');
include_once('./head.sub.php');
?>
<div style="padding: 10px;">
<div style="font-size: 17px; width: 350px; text-align: center;">
<form id="myForm" action="" class="form-example" onsubmit="return false;">
<div class="form-example">
<label for="name">Enter your name : </label>
<input type="text" name="name" id="name" required value="test">
</div>
<br>
<div class="form-example">
<label for="email">Enter your email : </label>
<input type="email" name="email" id="email" required value="*** 개인정보보호를 위한 이메일주소 노출방지 ***">
</div>
<br><br>
<div class="form-example">
<input type="submit" class="submit" value="get">
<input type="submit" class="submit" value="post">
<input type="submit" class="submit" value="put">
<input type="submit" class="submit" value="delete">
</div>
</form>
</div>
<br><br>
<div id="result" style="font-size: 17px;"></div>
</div>
<script>
$(function(){
$("#myForm").submit(function(){
var url = 'https://54.hull.kr/proxy.php';
var formData = $(this).serialize();
var method = $(document.activeElement).val();
$.ajax({
url : url,
type : method,
data : formData,
cache : false,
dataType:'json',
error : function(jqXHR, textStatus, errorThrown) {
alert(textStatus);
},
success : function(data, jqXHR, textStatus) {
$("#result").html(JSON.stringify(data));
setTimeout(function(){
$("#result").html("");
}, 5000);
}
});
return false;
});
});
</script>
<?php
include_once ('./tail.sub.php');
?>
위코드에서 sumbt 버튼에 따라 get,post,put,delete 방식으로 method를 설정할 수 있게 했습니다. resetful api와 규격을 맞추기 위해서죠. 내부에 proxy.php ajax로 통신하구요.
proxy.php는 순수하게 php로만 짯습니다. 함수와 class는 스텍오버플로우에서 가져와서 잘 안되는 오류를 수정 했습니다. 출처는 하두 정신없이 오래전에 검색해 둔거라 어딘지는 모르겠습니다.
class Params {
private $params = Array();
public function __construct() {
$this->_parseParams();
}
/**
* @brief Lookup request params
* @param string $name Name of the argument to lookup
* @param mixed $default Default value to return if argument is missing
* @returns The value from the GET/POST/PUT/DELETE value, or $default if not set
*/
public function get($name, $default = null) {
if (isset($this->params[$name])) {
return $this->params[$name];
} else {
return $default;
}
}
private function _parseParams() {
$method = $_SERVER['REQUEST_METHOD'];
if ($method == "PUT" || $method == "DELETE") {
parse_str(file_get_contents('php://input'), $this->params);
$GLOBALS["_{$method}"] = $this->params;
// Add these request vars into _REQUEST, mimicing default behavior, PUT/DELETE will override existing COOKIE/GET vars
$_REQUEST = $this->params + $_REQUEST;
} else{
$this->params = $_REQUEST;
}
}
}
function integralCurl(string $url, string $method = "GET", array $sendData = [], array $header = [], bool $isSsl = false, array $option = []) : array{
// $certificateLoc = $_SERVER['DOCUMENT_ROOT'] . "/inc/cacert.pem";
$certificateLoc = "";
$method = strtoupper($method);
$defaultOptions = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10
);
$ch = curl_init();
curl_setopt_array($ch, $defaultOptions);
curl_setopt($ch, CURLOPT_POST, $method === "POST");
if ($method === "POST") {
/*
$sendData 샘플
[
"a" => 1,
"b" => "22"
]
*/
if (count($sendData) >= 1) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $sendData);
}
} elseif ($method === "GET") {
if (count($sendData) >= 1) {
$paramsUrl = http_build_query($sendData);
$url .= "?" . $paramsUrl;
}
} elseif ($method === "PUT") {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
if (count($sendData) >= 1) {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($sendData));
}
} elseif ($method === "DELETE") {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
if (count($sendData) >= 1) {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($sendData));
}
}
curl_setopt($ch, CURLOPT_URL, $url);
if (count($option) >= 1) {
/*
$option 샘플
[
CURLOPT_HEADER => false,
CURLOPT_USERAGENT => "test"
]
*/
curl_setopt_array($ch, $option);
}
if (count($header) >= 1) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
if ($isSsl === true && $certificateLoc != "") {
curl_setopt($ch, CURLOPT_CAINFO, $certificateLoc);
}
$returnData = curl_exec($ch);
$returnState = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($returnData === false) {
$returnErr = "CURL ERROR: " . curl_error($ch);
} else {
$returnErr = "success";
}
curl_close($ch);
if($returnData){
$returnData = json_decode($returnData,true);
}
return [
"data" => $returnData,
"code" => $returnState,
"msg" => $returnErr
];
}
$method = $_SERVER["REQUEST_METHOD"];
$param = new Params();
$result = $_REQUEST;
$result['method'] = $method;
//$result['name'] = $param->get("name","");
//$result['email'] = $param->get("email","");
$headers = [
'accept: application/json,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-ch-ua: "Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"',
'sec-ch-ua-mobile: ?0',
'user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
];
echo json_encode(integralCurl("http://test.hull.kr/api.php", $method , $result, $headers));
new Params(); 으로 객체화를 진행하면 $_REQUEST 에 get,post,put,delete 상관없이 파싱되어 저장됩니다.
그걸 그대로 bbb.com/api.php 전송하는 것이죠. 전송할때도 기존 client.php에서 전송한 method 형식에 맞게 전송하기 위해 integralCurl() 함수를 사용합니다. integralCurl("http://test.hull.kr/api.php", $method , $result, $headers) 코드를 통해 http 서버에 기존 client.php에소 요청한 method 방식으로 데이터, 그리고 해더 값을 전송합니다.
이후 http://bbb.com/api.php 는 우리 영역이 아니므로 사실 고민할 필요는 없겠죠. 그곳에서 전송해주는 데이터만 echo 해주면 되니깐요.
가상의 http://bbb.com/api.php 를 개발해 봤습니다. 별 내용은 없습니다.
http://test.hull.kr/api.php 내용
class Params {
private $params = Array();
public function __construct() {
$this->_parseParams();
}
/**
* @brief Lookup request params
* @param string $name Name of the argument to lookup
* @param mixed $default Default value to return if argument is missing
* @returns The value from the GET/POST/PUT/DELETE value, or $default if not set
*/
public function get($name, $default = null) {
if (isset($this->params[$name])) {
return $this->params[$name];
} else {
return $default;
}
}
private function _parseParams() {
$method = $_SERVER['REQUEST_METHOD'];
if ($method == "PUT" || $method == "DELETE") {
parse_str(file_get_contents('php://input'), $this->params);
$GLOBALS["_{$method}"] = $this->params;
// Add these request vars into _REQUEST, mimicing default behavior, PUT/DELETE will override existing COOKIE/GET vars
$_REQUEST = $this->params + $_REQUEST;
} else{
$this->params = $_REQUEST;
}
}
}
$method = $_SERVER["REQUEST_METHOD"];
$param = new Params();
$result = $_REQUEST;
$result['method'] = $method;
//$result['name'] = $param->get("name","");
//$result['email'] = $param->get("email","");
echo json_encode($result);
그냥 api.php 입장에서는 GET/POST/PUT/DELETE 방식으로 전송받은 데이터를 그대로 ceho 해주는 프로그램입니다. 정상적 통신이 되었다는 의미로 mehod 방식도 확인하여 같이 ceho 해줍니다.
결과 입니다.
get
post
put
delete
다 이상 없이 통신이 가능했습니다.
간혹 aaa.com의 서버 개발 권한이 없으시면 ccc.com을 하나 세팅하시고 거기다 proxy.php 파일을 만들고 크로스도메인을 설정하시면되겠죠. ssl도 적용하시고
이상 허접한 팁이였습니다.
ps. 또 다른 방법으로는 php curl이 아니라 아예 php를 이용해서 os(리눅스 shell)에게 api 서버와 통신하게 하는 방법도 있습니다. 다음에 시간 나면 또 정리 해서 올리겠습니다.
!-->!-->!-->
10
댓글 10개
좋은 정보 얻어갑니다.
건강이 좋지 않아 병원에 있다가 이제서야 몸 추스리고 확인하네요.
행복한 하루되세요.