http1.1 mk III

amp_god 30.07.07 02:28

HTTP 1.1 asiakas, ylläpitää muodostetut yhteydet serveriin. :)

 Tekstiversio  Arvo: 7 (15 ääntä)  Äänestä: +  -
[code]
class http11 {
  ## Initial variables ##
  private $Connections = array();
  private $ConnTimeOut = 10;
  private $ErrorLog = array();
  private $DebugMSG = FALSE;

  ## Socket timeout set/get ##
  function TimeOut($seconds = FALSE) {
    if(!$seconds) {
      return $this->ConnTimeOut;
    }
    if(is_numeric($seconds)) {
      $this->ConnTimeOut = $seconds;
      return TRUE;
    } else {
      return FALSE;
    }
  }

  ## Debug set/get ##
  function Debug($value = NULL) {
    if($value == NULL) {
      return $this->DebugMSG;
    }
    if($value == TRUE) {
      $this->DebugMSG = TRUE;
      return TRUE;
    }
    if($value == FALSE) {
      $this->DebugMSG = FALSE;
      return TRUE;
    }
  }

  ## Connection handling ##
  private function Connect($server, $port) {
    if(!empty($this->Connections["$server:$port"])) {
      return($this->Connections["$server:$port"]);
    }
    $this->Connections["$server:$port"] = @fsockopen($server, $port, $errno, $errstr, $this->ConnTimeOut);
    if(!$this->Connections["$server:$port"]) {
      $this->ErrorLog[] = time() . " : Connection failed to $server:$port. $errno/$errstr";
      return FALSE;
    }
    return $this->Connections["$server:$port"];
  }

  ## HEAD Fetching ##
  private function GetHead($connection) {
    $buf = "";
    $continue = TRUE;
    while($continue) {
      if(!$connection) { $continue = FALSE; return FALSE; }      ## Connection failed?
      if(feof($connection)) { $continue = FALSE; return FALSE; } ## Unexpected EOF?
      if(substr($buf, -4) == "\r\n\r\n") { $continue = FALSE; break; }  ## Expected \r\n\r\n
      $buf .= fread($connection, 1);
    }
    $buf = "Status: " . trim($buf);
    $buf = explode("\r\n", $buf);
    $result = array();
    foreach($buf as $row) {
      $row = explode(": ", $row);
      $result["$row[0]"] = $row[1];
    }
    return $result;
  }

  ## Chunked decryption ##
  private function ReadChunked($connection) {
    $buf = "";
    $mainbuf = "";
    $chunklen = 1;
    if($this->DebugMSG) { print("Chunks:\n"); }
    while($chunklen > 0) {
      ##############################
      ## Finding the chunk lenght ##
      $temp = "";
      $buf = "";
      while(substr($buf, -2) != "\r\n" || strlen($temp) == 0) {
        $char = fread($connection, 1);
        $buf .= $char;
        if($char != "\r" && $char != "\n") {
          $temp .= $char;
        }
      }
      $chunklen = hexdec($temp);
      if($this->DebugMSG) { print("  Chunklen=$chunklen, Reads="); }
      ##/Finding the chink lenght ##
      ##############################
      $buf2 = "";
      if($this->DebugMSG) print("[");
      while(strlen($buf2) < $chunklen) {
        $buf2 .= fread($connection, $chunklen - strlen($buf2));
        if($this->DebugMSG) print(".");
      }
      if($this->DebugMSG) print("]\n");
      $mainbuf .= $buf2;
    }
    if($this->DebugMSG) { print("Chunks done\n"); }
    ##/Reading the CHUNKED encoding ##
    ##################################
    return $mainbuf;
  }

  ## DATA Fetching ##
  private function GetData($connection, $head) {
    if($this->DebugMSG) print("----Data download----\n");
    $mainbuf = "";
    $chunked = strpos($head["Transfer-Encoding"], "chunked");
    if($chunked === false) {
      #####################
      ## Normal Download ##
      $continue = TRUE;
      if($this->DebugMSG) print("  Downloading normally...\n");
      while($continue) {
        if(!$connection) { $continue = FALSE; return FALSE; break; } ## Connection failed?
        if(feof($connection)) { $continue = FALSE; break; }          ## EOF?
        if(!empty($head['Content-Length'])) {
          if(strlen($mainbuf) == $head['Content-Length']) { $continue = FALSE; break; } ## Lenght archieved
        }
        $new = fread($connection, 2048);
        $mainbuf .= $new;
        if($this->DebugMSG) print("    " . number_format(strlen($new)) . " bytes downloaded (" . number_format(strlen($mainbuf)) . " bytes total)\n");
        }
      if($this->DebugMSG) print("  Complete\n");
      ##/Normal Download ##
      #####################
    } else {
      ######################
      ## Chunked transfer ##
      $mainbuf = $this->ReadChunked($connection);
      ##/Chunked transfer ##
      ######################
    }
    ##########################
    ## GZipped transmission ##
    if($head['Content-Encoding'] == "gzip") {
      if($this->DebugMSG) { print("  GZipped transmission, decoding..."); }
      $mainbuf = gzinflate(substr($mainbuf, 10));
      if($this->DebugMSG) {
        if($mainbuf === false) {
          print(" Failed.\n");
        } else {
          print(" Success.\n");
        }
      }
    }
    ##/GZipped transmission ##
    ##########################
    if($this->DebugMSG) print("\n");
    return $mainbuf;
  }

  ## Generate http/1.1 request ##
  private function urlDefaults($url) {
    ##################
    ## URL Handling ##
    $url = parse_url($url);
    if(empty($url['port'])) { $url['port'] = 80; }
    if(empty($url['query'])) { $url['query'] = ""; }
    if(!empty($url['query'])) { $url['query'] = "?" . $url['query']; }
    if(empty($url['path'])) { $url['path'] = "/"; }
    ##/URL Handling ##
    ##################
    return $url;
  }

  private function genRequest($url, $method = "GET") {
    #####################
    ## Generating HEAD ##
    $url = $this->urlDefaults($url);
    $head = array("$method ${url['path']}${url['query']} HTTP/1.1",
                  "Host: ${url['host']}",
                  "Accept: */*",
                  "Accept-Encoding: gzip");
    if($this->DebugMSG) {
      print("----REQUEST----\n");
      print_r($head);
      print("\n");
    }
    ##/Generating HEAD ##
    #####################

    return $head;
  }

  ###########################
  ## GET http/1.1 function ##
  function GET($url) {
    $head = $this->genRequest($url);
    $url = $this->urlDefaults($url);

    ####################################
    ## Connecting and writing the req ##
    $sock = $this->Connect($url['host'], $url['port']);
    if(!$sock) { return FALSE; }
    foreach($head as $row) {
      fwrite($sock, "$row\r\n");
    }
    fwrite($sock, "\r\n");
    ##/Connecting and writing the req ##
    ####################################

    ##############################
    ## Getting the responseHead ##
    $responseHead = $this->GetHead($sock);
    if(!$responseHead) {
      return FALSE;
    }
    if($this->DebugMSG) {
      print("----RESPONSE----\n");
      print_r($responseHead);
      print("\n");
    }
    ##/Getting the responseHead ##
    ##############################

    ##########################
    ## Downloading the data ##
    $data = $this->GetData($sock, $responseHead);
    return $data;
    ##/Downloading the data ##
    ##########################
  }
  ##/GET http/1.1 function ##
  ###########################

  ###############
  ## HTTP HEAD ##
  function HEAD($url) {
    $head = $this->genRequest($url, "HEAD");
    $url = $this->urlDefaults($url);

    ####################################
    ## Connecting and writing the req ##
    $sock = $this->Connect($url['host'], $url['port']);
    if(!$sock) { return FALSE; }
    foreach($head as $row) {
      fwrite($sock, "$row\r\n");
    }
    fwrite($sock, "\r\n");
    ##/Connecting and writing the req ##
    ####################################

    ##############################
    ## Getting the responseHead ##
    $responseHead = $this->GetHead($sock);
    if(!$responseHead) {
      return FALSE;
    }
    return $responseHead;
  }
  ##/HTTP HEAD ##
  ###############

}
[/code]

## Todo ##
- yhteyden katkaiseminen "fiksusti"
- Chunked koodauksen purku voisi olla siistinpikin
- POST metodi
- Proxy
- referrerit sun muut.

## Käyttäminen ##
$httpClient = new http11;
$httpClient->Debug(TRUE);
$headinfo = $httpClient->HEAD("http://localhost/");
$data = $httpClient->GET("http://localhost");
print("---- Header ----\n");
print_r($headinfo);
print("\n");

print("----Final data----\n$data");



Tommonen. Tuli kirjoitettua http11 asiakas ihan puhtaalta pöydältä lukuunottamatta chunked koodauksen purkua.
Tarkoitettu lähinnä usean tiedoston lataamiseen samalta serveriltä ilman että katkaistaan yhteyttä jokavälissä.

Ehdotuksia?

amp_god 02:30 30.7.07 
Tulostaa esim seuraavaa: (debug päällä)
$ php http11_v3.php
X-Powered-By: PHP/5.2.1
Content-type: text/html

----REQUEST----
Array
(
    [0] => GET / HTTP/1.1
    [1] => Host: localhost
    [2] => Accept: */*
    [3] => Accept-Encoding: gzip, *
)

----RESPONSE----
Array
(
    [Status] => HTTP/1.1 200 OK
    [Transfer-Encoding] => chunked
    [X-Powered-By] => PHP/5.2.1
    [Set-Cookie] => PHPSESSID=b80006e458012abe322b20b9252de092; path=/
    [Expires] => Thu, 19 Nov 1981 08:52:00 GMT
    [Cache-Control] => no-store, no-cache, must-revalidate, post-check=0, pre-check=0
    [Pragma] => no-cache
    [Content-type] => text/html
    [Date] => Sun, 29 Jul 2007 23:30:14 GMT
    [Server] => lighttpd/1.4.13
)

----Data download----
Chunks:
  Chunklen=1087, Reads=[.]
  Chunklen=159, Reads=[.]
  Chunklen=0, Reads=[]
Chunks done

----Final data----
[[HTML Koodia]]
amp_god 19:13 30.7.07 
Chunked koodauksen luku siirretty omaan funktioonsa,
gzipin purku lisätty.
Entropia 17:23 1.8.07 
Montako HTTP-clienttiä täällä kuhassa oikein pitää olla?
editoitu: 22:57 1.8.07
amp_god 22:18 1.8.07 
Muut (minun tekemät) http asiakasohjelmat poistettu.

Näkeepähän ainakin että kehitystä tapahtuu ;)

-------------

Scriptiä päivitetty.
- Debug muuttujan määrääminen / palauttaminen Debug funktiolla.
- Yhdistämisen aikakatkaisun määrääminen / lukeminen TimeOut funktiolla.
- Muuttujat määrätty yksityisiksi
- Funktiot määrätty yksityisiksi
- HEAD kysely
- Lisää sisäisiä funktioita
tsuriga 12:58 3.8.07 
Ittehän kirjottaisin luokan isolla ja funkkarit pienellä alkukirjaimella, kuten PEARin ohjeissa ehdotellaan. Kivat kommentit ja pätkäkin varmaan toimii kuin junan vessa.
netteri 18:16 9.8.07 
Hmm, voisin vaikkapas poistaa oman ja nakata plussaa...
editoitu: 01:01 6.11.07
tsuriga 17:58 5.11.07 
Tuohon requestiin olis kiva lisätä omia headereitä ja tässä vois tietysti käyttää vaikkapa Zendin koodausohjeita niin sais selkeämmäks.