sThread
[ class tree: sThread ] [ index: sThread ] [ all elements ]

Source for file http.php

Documentation is available at http.php

  1. <?php
  2. /**
  3.  * sThread HTTP module
  4.  *
  5.  * HTTP protocol을 테스트 한다. (https는 지원하지 않는다.)
  6.  *
  7.  * 점검시에, 반환값이 200이 아닐경우 실패로 결과를 보내며, response
  8.  * header의 content-length와 실제 받은 데이터 사이즈가 동일해야 정상
  9.  * 처리로 판단을 한다.
  10.  *
  11.  * chunked 전송의 경우 chuned된 데이터를 파싱을 해서 사이즈가 맞는지
  12.  * 비교를 한다.
  13.  *
  14.  * <b>* 경고!</b>
  15.  * 4KB 이하의 문서에 사용하는 것을 권장한다. 만약 100K가 넘는 문서라면
  16.  * event_buffer_read 크기를 40960 정도로 증가 하는 것이 좋다.
  17.  *
  18.  *
  19.  * @category    Network
  20.  * @package     sThread
  21.  * @subpackage  sThread_Module
  22.  * @author      JoungKyun.Kim <http://oops.org>
  23.  * @copyright   (c) 2015 OOPS.ORG
  24.  * @license     BSD License
  25.  * @version     $Id$
  26.  * @link        http://pear.oops.org/package/sThread
  27.  * @filesource
  28.  */
  29.  
  30. /**
  31.  * sThread HTTP module
  32.  *
  33.  * HTTP protocol을 테스트 한다. (https는 지원하지 않는다.)
  34.  *
  35.  * 점검시에, 반환값이 200이 아닐경우 실패로 결과를 보내며, response
  36.  * header의 content-length와 실제 받은 데이터 사이즈가 동일해야 정상
  37.  * 처리로 판단을 한다.
  38.  *
  39.  * chunked 전송의 경우 chuned된 데이터를 파싱을 해서 사이즈가 맞는지
  40.  * 비교를 한다.
  41.  *
  42.  * HTTP 모듈에 사용할 수 있는 모듈 option은 다음과 같다.
  43.  *
  44.  * <ul>
  45.  *     <li><b>uri:</b>   URI</li>
  46.  *     <li><b>host:</b>  HTTP/1.1 Host header 값</li>
  47.  *     <li><b>agent:</b> User Agent 값</li>
  48.  *     <li><b>referer:</b> Referer 값</li>
  49.  * </ul>
  50.  *
  51.  * 예제:
  52.  * <code>
  53.  *   sThread::execute ('domain.com:80:http|uri=>/robot.txt,host=>www.domaing.com', 2, 'tcp');
  54.  * </code>
  55.  *
  56.  * <b>* 경고!</b>
  57.  * 4KB 이하의 문서에 사용하는 것을 권장한다. 만약 100K가 넘는 문서라면
  58.  * event_buffer_read 크기를 40960 정도로 증가 하는 것이 좋다.
  59.  *
  60.  * @category    Network
  61.  * @package     sThread
  62.  * @subpackage  sThread_Module
  63.  * @author      JoungKyun.Kim <http://oops.org>
  64.  * @copyright   (c) 2015 OOPS.ORG
  65.  * @license     BSD License
  66.  * @link        http://pear.oops.org/package/sThread
  67.  */
  68. Class sThread_HTTP {
  69.     // {{{ Base properties
  70.     /**#@+
  71.      * @access public
  72.      */
  73.     /**
  74.      * 이 변수의 값이 true로 셋팅이 되면, clear_session
  75.      * method를 만들어 줘야 한다. 반대로 false 상태에서는
  76.      * clear_session method가 존재하지 않아도 상관이 없다.
  77.      *
  78.      * @var bool 
  79.      */
  80.     static public $clearsession;
  81.     /**
  82.      * HTTP 모듈이 사용하는 protocol
  83.      * @var string 
  84.      */
  85.     static public $protocol;
  86.     /**
  87.      * HTTP 모듈이 사용하는 기본 포트 번호
  88.      * @var int 
  89.      */
  90.     static public $port;
  91.  
  92.     const HTTP_REQUEST  1;
  93.     const HTTP_RESPONSE 2;
  94.     const HTTP_CLOSE    3;
  95.     /**#@-*/
  96.     // }}}
  97.  
  98.     // {{{ Per module properties
  99.     /**#@+
  100.      * @access private
  101.      */
  102.     static private $uri  '/robots.txt';
  103.     //static private $uri   = '/index.php';
  104.     static private $agent 'pear.oops.org::sThread HTTP module';
  105.     static private $sess;
  106.     /**#@-*/
  107.     // }}}
  108.  
  109.     // {{{ (void) __construct (void)
  110.     /**
  111.      * Class OOP 형식 초기화 메소드
  112.      * @access public
  113.      * @return sThread_HTTP 
  114.      */
  115.     function __construct ({
  116.         self::init ();
  117.  
  118.         $this->clearsession = &self::$clearsession;
  119.         $this->port         = &self::$port;
  120.         $this->protocol     = &self::$protocol;
  121.         $this->uri          &self::$uri;
  122.         $this->agent        &self::$agent;
  123.         $this->sess         &self::$sess;
  124.     }
  125.     // }}}
  126.  
  127.     // {{{ (void) init (void)
  128.     /**
  129.      * HTTP 모듈을 초기화 한다.
  130.      *
  131.      * @access public
  132.      * @return void 
  133.      */
  134.     function init ({
  135.         self::$clearsession true;
  136.         self::$port         80;
  137.         self::$protocol     'tcp';
  138.         self::$sess = (object) array (
  139.             'returnCode' => array (),
  140.             'chunked'    => array (),
  141.             'length'     => array (),
  142.             'header'     => array (),
  143.             'data'       => array (),
  144.         );
  145.     }
  146.     // }}}
  147.  
  148.     // {{{ (int) sThread_HTTP::check_buf_status ($status)
  149.     /**
  150.      * 현재 상태가 event read 상태인지 event write 상태인지
  151.      * 를 판단한다.
  152.      *
  153.      * @access public
  154.      * @return int 
  155.      * @param  int    현재 status
  156.      */
  157.     function check_buf_status ($status{
  158.         switch ($status{
  159.             case :                         /* Vari::EVENT_CONNECT */
  160.             case self::HTTP_REQUEST :
  161.                 return Vari::EVENT_READY_SEND;
  162.                 break;
  163.             case self::HTTP_RESPONSE :
  164.                 return Vari::EVENT_READY_RECV;
  165.                 break;
  166.             case self::HTTP_CLOSE :
  167.                 return Vari::EVENT_READY_CLOSE;
  168.                 break;
  169.             default :
  170.                 return Vari::EVENT_UNKNOWN;
  171.         }
  172.     }
  173.     // }}}
  174.  
  175.     // {{{ (string) sThread_HTTP::call_status ($status, $call = false)
  176.     /**
  177.      * 현재의 status(integer) 또는 현재 status의 handler 이름을
  178.      * 반환한다.
  179.      *
  180.      * @access public
  181.      * @return int|string
  182.      * @param  int        현재 status
  183.      * @param  boolean    true로 설정했을 경우 현재 status의 handler
  184.      *                     이름을 반환한다.
  185.      */
  186.     function call_status ($status$call false{
  187.         switch ($status{
  188.             case self::HTTP_REQUEST :
  189.                 $r 'HTTP_REQUEST';
  190.                 break;
  191.             case self::HTTP_RESPONSE :
  192.                 $r 'HTTP_RESPONSE';
  193.                 break;
  194.             default:
  195.                 $r Vari::EVENT_UNKNOWN;
  196.         }
  197.  
  198.         if $call !== false && $r !== Vari::EVENT_UNKNOWN )
  199.             $r strtolower ($r);
  200.  
  201.         return $r;
  202.     }
  203.     // }}}
  204.  
  205.     // {{{ (boolean) sThread_HTTP::change_status (&$sess, $key)
  206.     /**
  207.      * 세션의 상태를 단계로 변경한다.
  208.      *
  209.      * @access public
  210.      * @param  boolean  변경한 상태가 마지막 단계일 경우 false를
  211.      *                   반환한다.
  212.      * @param  stdClass sThread 세션 변수 reference
  213.      * @param  int      세션 키
  214.      */
  215.     function change_status (&$sess$key{
  216.         ++$sess->status[$key];
  217.  
  218.         if $sess->status[$key=== self::HTTP_CLOSE )
  219.             return false;
  220.  
  221.         return true;
  222.     }
  223.     // }}}
  224.  
  225.     // {{{ (void) sThread_HTTP::set_last_status (&$sess, $key)
  226.     /**
  227.      * 세션의 상태를 마지막 단계로 변경한다.
  228.      *
  229.      * @access public
  230.      * @param  stdClass sThread 세션 변수 reference
  231.      * @param  int      세션 키
  232.      */
  233.     function set_last_status (&$sess$key{
  234.         $sess->status[$keyself::HTTP_CLOSE;
  235.     }
  236.     // }}}
  237.  
  238.     // {{{ (boolean) sThread_HTTP::clear_session ($key) {
  239.     /**
  240.      * session에서 사용한 변수(self::$sess)의 값을 정리한다.
  241.      *
  242.      * self::$clearsession == false 일 경우, clear_session method
  243.      * 는 존재하지 않아도 된다.
  244.      *
  245.      * @access public
  246.      * @return void 
  247.      * @param  int    세션 키
  248.      */
  249.     function clear_session ($key{
  250.         #Vari::objectUnset (self::$sess);
  251.         foreach self::$sess as $k => $val{
  252.             Vari::objectUnset (self::$sess->$k);
  253.         }
  254.  
  255.         $sess Vari::$sess;
  256.         if empty ($sess->recv[$key]) )
  257.             $sess->recv[$keybase64_encode ($sess->recv[$key]);
  258.     }
  259.     // }}}
  260.  
  261.     /*
  262.      * Handler 정의
  263.      *
  264.      * Handler는 call_status 메소드에 정의된 값들 중
  265.      * Vari::EVENT_UNKNOWN를 제외한 모든 status의 constant string을
  266.      * 소문자로해서 만들어야 한다.
  267.      *
  268.      * Handler 이름은 sThread_MODULE::call_status 메소드를
  269.      * 통해서 구할 수 있다.
  270.      *
  271.      * handler는 다음의 구조를 가지며, 실제로 전송을 하거나 받는
  272.      * 것은 libevent가 한다.
  273.      *
  274.      * write handler:
  275.      *       handler_name (&$ses, $key)
  276.      *
  277.      *       write handler는 실제로 전송을 하지 않고 전송할
  278.      *       데이터를 생성해서 반환만 한다.
  279.      *
  280.      * read handler:
  281.      *       handler_name (&$sess, $key, $recv) 
  282.      *
  283.      *       read handler의 반환값은 다음과 같이 지정을 해야 한다.
  284.      *
  285.      *       true  => 모든 전송이 완료
  286.      *       false => 전송 받을 것이 남아 있음
  287.      *       null  => 전송 중 에러 발생
  288.      *
  289.      *       이 의미는 sThread가 read handler에서 결과값에 따라
  290.      *       true는 다음 단계로 전환을 하고, false는 현 status를
  291.      *       유지하며, null의 경우 connection을 종료를 한다.
  292.      */
  293.  
  294.     // {{{ (void) sThread_HTTP::http_request (&$sess, $key)
  295.     /**
  296.      * HTTP 요청 데이터를 반환
  297.      *
  298.      * @access public
  299.      * @return void 
  300.      * @param  stdClass 세션 object
  301.      * @param  int      세션 키
  302.      */
  303.     function http_request (&$sess$key{
  304.         list ($host$port$type$sess->addr[$key];
  305.         $opt $sess->opt[$key];
  306.  
  307.         $uri = isset ($opt->uri$opt->uri self::$uri;
  308.         $hostHeader = isset ($opt->host$opt->host $host;
  309.         $agent = isset ($opt->agent$opt->agent self::$agent;
  310.         if $opt->referer )
  311.             $referer sprintf ("Referer: %s\r\n"$opt->referer);
  312.  
  313.         return "GET {$uri} HTTP/1.1\r\n.
  314.                 "Host: {$hostHeader}\r\n.
  315.                 "Accept: *.*\r\n" .
  316.                 $referer .
  317.                 "User-Agent: {$agent}\r\n.
  318.                 "Connection: close\r\n" .
  319.                 "\r\n";
  320.     }
  321.     // }}}
  322.  
  323.     // {{{ (boolean) sThread_HTTP::http_response (&$sess, $key, $recv)
  324.     /**
  325.      * 서버의 응답을 확인
  326.      *
  327.      * @access public
  328.      * @return bool|null결과 값은 다음과 같다.
  329.      *      <ul>
  330.      *          <li>true:  모든 전송이 완료</li>
  331.      *          <li>false: 전송할 것이 남아 있음. readcallback에서
  332.      *                     false를 받으면 status를 유지한다.</li>
  333.      *          <li>null:  전송 중 에러 발생</li>
  334.      *      </ul>
  335.      * @param  stdClass 세션 object
  336.      * @param  int      세션 키
  337.      * @param  mixed    read callback에서 전송받은 누적 데이터
  338.      */
  339.     function http_response (&$sess$key$recv{
  340.         if $recv )
  341.             return false;
  342.  
  343.         list ($host$port$type$sess->addr[$key];
  344.         # remove extra option
  345.         $type preg_replace ('/\|.*/'''$type);
  346.         $sess->recv[$key.= $recv;
  347.  
  348.         /*
  349.          * Check HTTP Return Code
  350.          */
  351.         if isset (self::$sess->returnCode[$key]) ) {
  352.             if preg_match ('!^HTTP/[0-9]\.[0-9] ([0-9]{3})!'$recv$matches) ) {
  353.                 Vari::$res->status[$keyarray (
  354.                     "{$host}:{$port}",
  355.                     false,
  356.                     'Protocol error: Not http protocol'
  357.                 );
  358.                 self::clear_session ($key);
  359.                 return null;
  360.             }
  361.  
  362.             if $matches[1!= '200' {
  363.                 Vari::$res->status[$keyarray (
  364.                     "{$host}:{$port}",
  365.                     false,
  366.                     "Protocol error: Return code is not 200 (Return {$matches[1]})"
  367.                 );
  368.                 self::clear_session ($key);
  369.                 return null;
  370.             }
  371.  
  372.             self::$sess->returnCode[$key$matches[1];
  373.         }
  374.  
  375.         $headerSet false;
  376.         if isset (self::$sess->header[$key]&&
  377.              ($pos strpos ($sess->recv[$key]"\r\n\r\n")) !== false {
  378.             $headerSet true;
  379.             self::parse_header ($keytrim (substr ($sess->recv[$key]0$pos)));
  380.             $data substr ($sess->recv[$key]$pos 4);
  381.  
  382.             if isset (self::$sess->header[$key]->Transfer_Encoding&&
  383.                  self::$sess->header[$key]->Transfer_Encoding == "chunked" {
  384.                 self::$sess->chunked[$keytrue;
  385.                 self::$sess->length[$key0;
  386.             else {
  387.                 self::$sess->length[$key= (integer) self::$sess->header[$key]->Content_Length;
  388.             }
  389.             self::$sess->data[$key$data;
  390.         }
  391.  
  392.         if isset (self::$sess->data[$key]&& $headerSet !== true )
  393.             self::$sess->data[$key.= $recv;
  394.  
  395.         /*
  396.          * Procotol complete case
  397.          */
  398.         $exit false;
  399.         // case chunnked encoding
  400.         if self::$sess->chunked[$key=== true {
  401.             if preg_match ("/(^0(\r\n)+|\r\n+0(\r\n)+)$/"$recv) ) {
  402.                 //file_put_contents ('./a', self::$sess->data[$key]);
  403.                 self::chunked_data ($key);
  404.                 /*
  405.                 echo "!!!!!!!!!!!!!!!!!!!!!-----------------\n";
  406.                 echo self::$sess->data[$key] . "\n";
  407.                 echo "1. " . strlen (self::$sess->data[$key]) . "\n";
  408.                 echo "2. " . self::$sess->length[$key] . "\n";
  409.                 echo "!!!!!!!!!!!!!!!!!!!!!-----------------\n";
  410.                 file_put_contents ('./b', self::$sess->data[$key]);
  411.                 file_put_contents ('./c', rtrim ($sess->recv[$key]));
  412.                  */
  413.                 $datalen strlen (self::$sess->data[$key]);
  414.                 $chunklen self::$sess->length[$key];
  415.                 if $datalen != $chunklen {
  416.                     Vari::$res->status[$keyarray (
  417.                         "{$host}:{$port}",
  418.                         false,
  419.                         "Protocol error: Contents Length different (D: $datalen <-> C: $chunklen)"
  420.                     );
  421.                     return null;
  422.                 }
  423.                 $exit true;
  424.             }
  425.         else {
  426.             if strlen (self::$sess->data[$key]== self::$sess->length[$key)
  427.                 $exit true;
  428.         }
  429.  
  430.         if $exit === true {
  431.             if Vari::$result === true {
  432.                 $sess->data[$key= (object) array (
  433.                     'returnCode' => self::$sess->returnCode[$key],
  434.                     'length'     => self::$sess->length[$key],
  435.                     'value'      => base64_encode (self::$sess->data[$key]),
  436.                     'header'     => null
  437.                 );
  438.                 Vari::objectCopy ($sess->data[$key]->headerself::$sess->header[$key]);
  439.             }
  440.             unset (Vari::$sess->recv[$key]);
  441.  
  442.             self::clear_session ($key);
  443.             return true;
  444.         }
  445.  
  446.         return false;
  447.     }
  448.     // }}}
  449.  
  450.     /*
  451.      * User define method
  452.      */
  453.     // {{{ private (void) sThread_HTTP::parse_header ($v)
  454.     private function parse_header ($key$v{
  455.         $s explode ("\r\n"$v);
  456.         foreach $s as $line {
  457.             if preg_match ('/([^:]+): (.+)/'$line$matches) )
  458.                 continue;
  459.             $matches[1str_replace ('-''_'$matches[1]);
  460.             Vari::objectInit (self::$sess->header[$key]);
  461.             self::$sess->header[$key]->{$matches[1]$matches[2];
  462.         }
  463.     }
  464.     // }}}
  465.  
  466.     // {{{ private (int) sThread_HTTP::chunked_length ($v)
  467.     private function chunked_length ($v{
  468.         return hexdec (trim ($v));
  469.     }
  470.     // }}}
  471.  
  472.     // {{{ private sThread_HTTP::chunked_data ($key)
  473.     private function chunked_data ($key{
  474.         $p self::$sess->data[$key];
  475.         $hexlen 0;
  476.         while $p && $hexv != "0" {
  477.             $hexlen $hexlen 8;
  478.             $hexv preg_replace ('/[\s]+.*/'''trim (substr ($p0$hexlen)));
  479.             # hex 문자열이 4보다 작을 경우, 한문자가 땡겨지므로 처리해 줘야 한다.
  480.             if strlen ($hexv)
  481.                 $hexlen -= strlen ($hexv);
  482.  
  483.             $chunklen self::chunked_length ($hexv);
  484.             $buf .= substr ($p$hexlen$chunklen);
  485.             $p substr ($p$hexlen $chunklen);
  486.             self::$sess->length[$key+= $chunklen;
  487.         }
  488.  
  489.         self::$sess->data[$key$buf;
  490.     }
  491.     // }}}
  492. }

Documentation generated on Tue, 14 May 2019 02:00:05 +0900 by phpDocumentor 1.4.4