https에서 http와 ajax 통신시 proxy 프로그램 만들기 > 개발자팁

개발자팁

개발과 관련된 유용한 정보를 공유하세요.
질문은 QA에서 해주시기 바랍니다.

https에서 http와 ajax 통신시 proxy 프로그램 만들기 정보

PHP https에서 http와 ajax 통신시 proxy 프로그램 만들기

본문

QA 게시판에서 제목과 같은 질문이 나와서 생각난김에 정리 해서 올립니다.

 

3422125589_1655359006.3347.png

 

위 그림을 보시면 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과 통신을 시도할 수 있습니다.

 

3422125589_1655359406.7853.png

 

위 그림처럼 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

3422125589_1655360513.4694.png

 

 

post

3422125589_1655360557.5261.png

 

put

3422125589_1655360606.5899.png

 

delete

3422125589_1655360665.8617.png

 

 

다 이상 없이 통신이 가능했습니다.

 

간혹 aaa.com의 서버 개발 권한이 없으시면 ccc.com을 하나 세팅하시고 거기다 proxy.php 파일을 만들고 크로스도메인을 설정하시면되겠죠. ssl도 적용하시고

 

이상 허접한 팁이였습니다.

 

ps. 또 다른 방법으로는 php curl이 아니라 아예 php를 이용해서 os(리눅스 shell)에게 api 서버와 통신하게 하는 방법도 있습니다.  다음에 시간 나면 또 정리 해서 올리겠습니다.

 

추천
10

댓글 10개

@나리스 넵 선택사항이죠. ^^ 질문에서는 선제조건이 프론트에서 통신이 안되서 해결책을 제시했던거구요. 서버사이드에선 아무꺼나 써도되죠. 요즘은 프론트, 서버 개발로 나뉘니깐 프론트 개발자에게 api 던져 주는 개념으로 생각하시면 될 듯 합니다.
소중한 시간내서 설명해 주셔서 너무 감사합니다.
건강이 좋지 않아 병원에 있다가 이제서야 몸 추스리고 확인하네요.
행복한 하루되세요.
전체 5,352
개발자팁 내용 검색

회원로그인

(주)에스아이알소프트 / 대표:홍석명 / (06211) 서울특별시 강남구 역삼동 707-34 한신인터밸리24 서관 1404호 / E-Mail: admin@sir.kr
사업자등록번호: 217-81-36347 / 통신판매업신고번호:2014-서울강남-02098호 / 개인정보보호책임자:김민섭(minsup@sir.kr)
© SIRSOFT