HTTP1.1 Client

amp_god 22.07.08 20:50

HTTP 1.1 Asiakas joka (tulee) osaamaan HTTP GET/HEAD/POST -funktiot ja tietenkin HTTP1.1:en vaatima Chunked -koodauksen

 Tekstiversio  Arvo: 1 (1 ääntä)  Äänestä: +  -
<?php
class http11 {
  private $socket = FALSE;
  private $proxyHost = FALSE;
  private $proxyPort = FALSE;
  private $errorString = FALSE;
  private $warningString = FALSE;
  private $currentSocket = FALSE;
  private $cookies = FALSE;

  function readUntil($mark) {
    if(!$this->currentSocket) { return FALSE; }
    $buf = '';
    $markLen = strlen($mark) * -1;
    while(substr($buf, $markLen) != $mark) {
      if(feof($this->currentSocket)) break;
      $buf .= fread($this->currentSocket, 1);
    }
    return $buf;
  }

  function readLenght($lenght) {
    $buf = "";
    while(strlen($buf) < $lenght) {
      if(feof($this->currentSocket)) break;
      $buf .= fread($this->currentSocket, $lenght - strlen($buf));
      print(".");
    }
    return $buf;
  }

  function setProxy($proxyHost) {
    $proxyHost = parse_url($proxyHost);
    if(empty($proxyHost['host'])) { return FALSE; }
    if(empty($proxyHost['port'])) { return FALSE; }
    $this->proxyHost = $proxyHost['host'];
    $this->proxyPort = $proxyHost['port'];
    return TRUE;
  }

  function connect($url) {
    $url = parse_url($url);
    if(empty($url['port'])) { $url['port'] = 80; }
    $errNo = FALSE;
    $errStr = FALSE;
    if(!empty($this->proxyHost)) {
      $connect = @fsockopen($this->proxyHost, $this->proxyPort, $errNo, $errStr);
      if(!$connect) {
        $this->errorString[] = "Connection failure, $errStr ($errNo)";
        return FALSE;
      }
      $this->socket[$url['host']] = $connect;
      $this->currentSocket = $connect;
      return TRUE;
    }
    $connect = @fsockopen($url['host'], $url['port'], $errNo, $errStr);
    if(!$connect) {
      $this->errorString[] = "Connection failure, $errStr ($errNo)";
      return FALSE;
    }
    $this->socket[$url['host']] = $connect;
    $this->currentSocket = $connect;
    return TRUE;
  }

  function genRequest($method, $url, $postArray = FALSE) {
    $parsedUrl = parse_url($url);
    $request = array();
    if(empty($parsedUrl['path'])) { $parsedUrl['path'] = "/"; }
    if(empty($parsedUrl['query'])) {
      $path = $parsedUrl['path'];
    } else {
      $path = $parsedUrl['path'] . "?" . $parsedUrl['query'];
    }
    if(!empty($this->cookies[$parsedUrl['host']])) {
      $cookies = "";
      foreach($this->cookies[$parsedUrl['host']] as $cookie) {
        $cookies .= "Cookie: $cookie\r\n";
      }
    } else {
      $cookies = FALSE;
    }
    if(!$this->proxyHost) {
      switch($method) {
        case 'GET':
          $request[] = "GET $path HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          $request[] = "Accept-Encoding: compress, gzip\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
        break;
        case 'HEAD':
          $request[] = "HEAD $path HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
        break;
        case 'POST':
          $postString = "";
          $last = "";
          foreach($postArray AS $name => $content) {
            $postString .= "$name=$content&";
            $last = $name;
          }
          $request[] = "POST $path HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          $request[] = "Accept-Encoding: gzip\r\n";
          $request[] = "Content-Lenght: " . strlen($postString) . "\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
          $request[] = $postString;
        break;
      }
    }
    if($this->proxyHost) {
      switch($method) {
        case 'GET':
          $request[] = "GET $url HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          $request[] = "Accept-Encoding: gzip\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
        break;
        case 'HEAD':
          $request[] = "HEAD $url HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
        break;
        case 'POST':
          $postString = "";
          foreach($postArray AS $name => $content) {
            $postString .= "$name=$content&";
          }
          $postString = substr($postString, 0, -1);
          $request[] = "POST $url HTTP/1.1\r\n";
          $request[] = "Host: " . $parsedUrl['host'] . "\r\n";
          $request[] = "Accept-Encoding: gzip\r\n";
          $request[] = "Content-Lenght: " . strlen($postString) . "\r\n";
          if($cookies) { $request[] = "$cookies"; }
          $request[] = "Connection: close\r\n";
          $request[] = "\r\n";
          $request[] = $postString;
        break;
      }
    }
    return $request;
  }

  function readChunked() {
    $mainBuf = "";
    $chunkLen = 1;
    while($chunkLen > 0) {
      $temp = substr($this->readUntil("\r\n"), 0, -2);
      if($temp == '') {
        $temp = substr($this->readUntil("\r\n"), 0, -2);
      }
      $chunkLen = hexdec($temp);
      print("\nChunklen=$chunkLen");
      if($chunkLen > 0) {
        $mainBuf .= $this->readLenght($chunkLen);
      }
    }
    return $mainBuf;
  }

  function sendRequest($request) {
    if(!is_array($request)) {
      $this->errorString[] = "Generated request is not an array";
      return FALSE;
    }
    foreach($request as $req) {
      fwrite($this->currentSocket, $req);
    }
    return TRUE;
  }

  function readHeader() {
    $buf = $this->readUntil("\r\n\r\n");
    if(strlen($buf) == 0) {
      $this->errorString[] = "No header.";
      return FALSE;
    }
    $result = array();
    $count = 0;
    $buf = explode("\r\n", $buf);
    foreach($buf AS $line) {
      if($count == 0) {
        $result['Status'] = $line;
        } else {
        $lineSplit = explode(": ", $line);
        if(!empty($result[$lineSplit[0]])) {
          if(is_array($result[$lineSplit[0]])) {
            $result[$lineSplit[0]][] = $lineSplit[1];
            } else {
            $result[$lineSplit[0]] = array($result[$lineSplit[0]], $lineSplit[1]);
            }
          } else {
          $result[$lineSplit[0]] = $lineSplit[1];
        }
      }
      $count++;
    }
    return $result;
  }

  function readResponse($header) {
    if($header['Transfer-Encoding'] == 'chunked') {
      $buf = $this->readChunked();
    } else {
      $buf = '';
      while(!feof($this->currentSocket)) {
        $buf .= fread($this->currentSocket, 1024);
      }
    }
    if(!empty($header['Content-Length'])) {
      if(strlen($buf) != $header['Content-Length']) {
        $this->warningString[] = "Received data lenght doesnt match header...\n";
      }
    }
    if($header['Content-Encoding'] == 'gzip') {
      $buf = gzinflate(substr($buf, 10));
    }
    return $buf;
  }

  function saveCookies($header, $url) {
    if(empty($header) || empty($url)) { return FALSE; }
    if(!is_array($header['Set-Cookie'])) { return FALSE; }
    $url = parse_url($url);
    $cookies = array();
    foreach($header['Set-Cookie'] as $cookie) {
      $cookie = explode('; ', $cookie);
      $cookies[] = $cookie[0];
    }
    $this->cookies[$url['host']] = $cookies;
    return TRUE;
  }

  function GET($url) {
    $url_parsed = parse_url($url);
    $request = $this->genRequest('GET', $url);
    print("Generated request:\n"); print_r($request);
    print("Connecting...");
    if(!$this->connect($url)) { return FALSE; }
    print("\nSending the request...");
    $reqSend = $this->sendRequest($request);
    print("\nReading the response header...");
    $header = $this->readHeader();
    if(!$header) {
      $this->errorString[] = "Header retrieval failure.";
      return FALSE;
    }
    print("\nHeader received:\n"); print_r($header);
    $this->saveCookies($header, $url);
    print("Reading the rest...");
    if(!empty($header['Location'])) {
      return $this->GET("http://" . $url_parsed['host'] . $url_parsed['path'] . $header['Location']);
    }
    $theData = $this->readResponse($header);
    if(!$theData) {
      $this->errorString[] = "Error retrieving the data...";
      return FALSE;
    }
    print("\n\nThe data:\n$theData<EOF>");
  }
  function Errors() {
    return $this->errorString;
  }
 
  function Warnings() {
    return $this->warningString;
  }

  function Cookies() {
    return $this->cookies;
  }

}

## ToDo ##
- HEAD
- POST

## Done ##
- Keksien tallennus
- gzip
- Proxy
- GET
- Chunked purku

# Esimerkki #
$client = new http11();
print($client->GET("http://www.google.fi/"));

# Esimerkki 2 #
$client = new http11();
$client->setProxy("http://localhost:3128/");
print($client->GET("http://www.google.fi/"));
 

Akiro 20:53 22.7.08 
Miten tämä eroaa tuosta edellisestäsi: http://mureakuha.com/koodikirjasto/1022
Ja miksi se keskeneräisenä pitää esitellä?
amp_god 20:58 22.7.08 
Akiro kirjoitti:
Miten tämä eroaa tuosta edellisestäsi: http://mureakuha.com/koodikirjasto/1022
Ja miksi se keskeneräisenä pitää esitellä?


Suurin muutos on tuo POST -funkkari.
Toinen etu on 100% alusta lähtien kirjoitettu. :)

Keskeneräisenä "julkaisemisen" syy:
Kirjoittelen tätä työn ohessa ja joudun käyttämään muutamaa eri konetta + kotikoneella vielä erikseen.
Akiro 21:13 22.7.08 
Ok, aiheutti vain ihmettelyä tuolla ircin puolella. Pätevän oloinen koodipätkähän tämä muuten on.

PS. On niitä muitakin tapoja säilöä tekstiä/koodia netissä ;-)