Source for file http.php
Documentation is available at http.php
* HTTP protocol을 테스트 한다. (https는 지원하지 않는다.)
* 점검시에, 반환값이 200이 아닐경우 실패로 결과를 보내며, response
* header의 content-length와 실제 받은 데이터 사이즈가 동일해야 정상
* chunked 전송의 경우 chuned된 데이터를 파싱을 해서 사이즈가 맞는지
* 4KB 이하의 문서에 사용하는 것을 권장한다. 만약 100K가 넘는 문서라면
* event_buffer_read 크기를 40960 정도로 증가 하는 것이 좋다.
* @subpackage sThread_Module
* @author JoungKyun.Kim <http://oops.org>
* @copyright (c) 2015 OOPS.ORG
* @link http://pear.oops.org/package/sThread
* HTTP protocol을 테스트 한다. (https는 지원하지 않는다.)
* 점검시에, 반환값이 200이 아닐경우 실패로 결과를 보내며, response
* header의 content-length와 실제 받은 데이터 사이즈가 동일해야 정상
* chunked 전송의 경우 chuned된 데이터를 파싱을 해서 사이즈가 맞는지
* HTTP 모듈에 사용할 수 있는 모듈 option은 다음과 같다.
* <li><b>uri:</b> URI</li>
* <li><b>host:</b> HTTP/1.1 Host header 값</li>
* <li><b>agent:</b> User Agent 값</li>
* <li><b>referer:</b> Referer 값</li>
* sThread::execute ('domain.com:80:http|uri=>/robot.txt,host=>www.domaing.com', 2, 'tcp');
* 4KB 이하의 문서에 사용하는 것을 권장한다. 만약 100K가 넘는 문서라면
* event_buffer_read 크기를 40960 정도로 증가 하는 것이 좋다.
* @subpackage sThread_Module
* @author JoungKyun.Kim <http://oops.org>
* @copyright (c) 2015 OOPS.ORG
* @link http://pear.oops.org/package/sThread
* 이 변수의 값이 true로 셋팅이 되면, clear_session
* method를 만들어 줘야 한다. 반대로 false 상태에서는
* clear_session method가 존재하지 않아도 상관이 없다.
// {{{ Per module properties
static private $uri = '/robots.txt';
//static private $uri = '/index.php';
static private $agent = 'pear.oops.org::sThread HTTP module';
// {{{ (void) __construct (void)
$this->port = &self::$port;
$this->uri = &self::$uri;
$this->agent = &self::$agent;
$this->sess = &self::$sess;
// {{{ (void) init (void)
self::$clearsession = true;
self::$sess = (object) array (
'returnCode' => array (),
// {{{ (int) sThread_HTTP::check_buf_status ($status)
* 현재 상태가 event read 상태인지 event write 상태인지
case 0 : /* Vari::EVENT_CONNECT */
case self::HTTP_REQUEST :
return Vari::EVENT_READY_SEND;
case self::HTTP_RESPONSE :
return Vari::EVENT_READY_RECV;
return Vari::EVENT_READY_CLOSE;
return Vari::EVENT_UNKNOWN;
// {{{ (string) sThread_HTTP::call_status ($status, $call = false)
* 현재의 status(integer) 또는 현재 status의 handler 이름을
* @param boolean true로 설정했을 경우 현재 status의 handler
case self::HTTP_REQUEST :
case self::HTTP_RESPONSE :
$r = Vari::EVENT_UNKNOWN;
if ( $call !== false && $r !== Vari::EVENT_UNKNOWN )
// {{{ (boolean) sThread_HTTP::change_status (&$sess, $key)
* @param boolean 변경한 상태가 마지막 단계일 경우 false를
* @param stdClass sThread 세션 변수 reference
if ( $sess->status[$key] === self::HTTP_CLOSE )
// {{{ (void) sThread_HTTP::set_last_status (&$sess, $key)
* @param stdClass sThread 세션 변수 reference
$sess->status[$key] = self::HTTP_CLOSE;
// {{{ (boolean) sThread_HTTP::clear_session ($key) {
* session에서 사용한 변수(self::$sess)의 값을 정리한다.
* self::$clearsession == false 일 경우, clear_session method
#Vari::objectUnset (self::$sess);
foreach ( self::$sess as $k => $val) {
Vari::objectUnset (self::$sess->$k);
if ( ! empty ($sess->recv[$key]) )
$sess->recv[$key] = base64_encode ($sess->recv[$key]);
* Handler는 call_status 메소드에 정의된 값들 중
* Vari::EVENT_UNKNOWN를 제외한 모든 status의 constant string을
* Handler 이름은 sThread_MODULE::call_status 메소드를
* handler는 다음의 구조를 가지며, 실제로 전송을 하거나 받는
* handler_name (&$ses, $key)
* write handler는 실제로 전송을 하지 않고 전송할
* handler_name (&$sess, $key, $recv)
* read handler의 반환값은 다음과 같이 지정을 해야 한다.
* false => 전송 받을 것이 남아 있음
* 이 의미는 sThread가 read handler에서 결과값에 따라
* true는 다음 단계로 전환을 하고, false는 현 status를
* 유지하며, null의 경우 connection을 종료를 한다.
// {{{ (void) sThread_HTTP::http_request (&$sess, $key)
* @param stdClass 세션 object
list ($host, $port, $type) = $sess->addr[$key];
$uri = isset ($opt->uri) ? $opt->uri : self::$uri;
$hostHeader = isset ($opt->host) ? $opt->host : $host;
$agent = isset ($opt->agent) ? $opt->agent : self::$agent;
$referer = sprintf ("Referer: %s\r\n", $opt->referer);
return "GET {$uri} HTTP/1.1\r\n" .
"Host: {$hostHeader}\r\n" .
"User-Agent: {$agent}\r\n" .
"Connection: close\r\n" .
// {{{ (boolean) sThread_HTTP::http_response (&$sess, $key, $recv)
* @return bool|null결과 값은 다음과 같다.
* <li>true: 모든 전송이 완료</li>
* <li>false: 전송할 것이 남아 있음. readcallback에서
* false를 받으면 status를 유지한다.</li>
* <li>null: 전송 중 에러 발생</li>
* @param stdClass 세션 object
* @param mixed read callback에서 전송받은 누적 데이터
list ($host, $port, $type) = $sess->addr[$key];
$sess->recv[$key] .= $recv;
if ( ! isset (self::$sess->returnCode[$key]) ) {
if ( ! preg_match ('!^HTTP/[0-9]\.[0-9] ([0-9]{3})!', $recv, $matches) ) {
Vari::$res->status[$key] = array (
'Protocol error: Not http protocol'
self::clear_session ($key);
if ( $matches[1] != '200' ) {
Vari::$res->status[$key] = array (
"Protocol error: Return code is not 200 (Return {$matches[1]})"
self::clear_session ($key);
self::$sess->returnCode[$key] = $matches[1];
if ( ! isset (self::$sess->header[$key]) &&
($pos = strpos ($sess->recv[$key], "\r\n\r\n")) !== false ) {
self::parse_header ($key, trim (substr ($sess->recv[$key], 0, $pos)));
$data = substr ($sess->recv[$key], $pos + 4);
if ( isset (self::$sess->header[$key]->Transfer_Encoding) &&
self::$sess->header[$key]->Transfer_Encoding == "chunked" ) {
self::$sess->chunked[$key] = true;
self::$sess->length[$key] = 0;
self::$sess->length[$key] = (integer) self::$sess->header[$key]->Content_Length;
self::$sess->data[$key] = $data;
if ( isset (self::$sess->data[$key]) && $headerSet !== true )
self::$sess->data[$key] .= $recv;
// case chunnked encoding
if ( self::$sess->chunked[$key] === true ) {
if ( preg_match ("/(^0(\r\n)+|\r\n+0(\r\n)+)$/", $recv) ) {
//file_put_contents ('./a', self::$sess->data[$key]);
self::chunked_data ($key);
echo "!!!!!!!!!!!!!!!!!!!!!-----------------\n";
echo self::$sess->data[$key] . "\n";
echo "1. " . strlen (self::$sess->data[$key]) . "\n";
echo "2. " . self::$sess->length[$key] . "\n";
echo "!!!!!!!!!!!!!!!!!!!!!-----------------\n";
file_put_contents ('./b', self::$sess->data[$key]);
file_put_contents ('./c', rtrim ($sess->recv[$key]));
$datalen = strlen (self::$sess->data[$key]);
$chunklen = self::$sess->length[$key];
if ( $datalen != $chunklen ) {
Vari::$res->status[$key] = array (
"Protocol error: Contents Length different (D: $datalen <-> C: $chunklen)"
if ( strlen (self::$sess->data[$key]) == self::$sess->length[$key] )
if ( Vari::$result === true ) {
$sess->data[$key] = (object) array (
'returnCode' => self::$sess->returnCode[$key],
'length' => self::$sess->length[$key],
'value' => base64_encode (self::$sess->data[$key]),
Vari::objectCopy ($sess->data[$key]->header, self::$sess->header[$key]);
unset (Vari::$sess->recv[$key]);
self::clear_session ($key);
// {{{ private (void) sThread_HTTP::parse_header ($v)
private function parse_header ($key, $v) {
foreach ( $s as $line ) {
if ( ! preg_match ('/([^:]+): (.+)/', $line, $matches) )
self::$sess->header[$key]->{$matches[1]} = $matches[2];
// {{{ private (int) sThread_HTTP::chunked_length ($v)
private function chunked_length ($v) {
// {{{ private sThread_HTTP::chunked_data ($key)
private function chunked_data ($key) {
$p = self::$sess->data[$key];
while ( $p && $hexv != "0" ) {
$hexlen = ! $hexlen ? 6 : 8;
# hex 문자열이 4보다 작을 경우, 한문자가 땡겨지므로 처리해 줘야 한다.
$hexlen -= 4 - strlen ($hexv);
$chunklen = self::chunked_length ($hexv);
$buf .= substr ($p, $hexlen, $chunklen);
$p = substr ($p, $hexlen + $chunklen);
self::$sess->length[$key] += $chunklen;
self::$sess->data[$key] = $buf;
|