Skip to content

Instantly share code, notes, and snippets.

@luciferous
Created October 9, 2010 12:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save luciferous/618157 to your computer and use it in GitHub Desktop.
Save luciferous/618157 to your computer and use it in GitHub Desktop.
A very small HTTP Client with Rack/WSGI style interfaces

Tiny HTTP

A very small HTTP client with Rack/WSGI style interfaces.

The setup

$sid = 'ACxxxxxxxxxxxx';
$token = '12345678';
$http = new TinyHttp("https://$sid:$token@api.twilio.com");

Make a request

Tiny takes a 3-tuple comprising the request path, headers, and body. Only the request path is required, the headers and body are optional.

$response = $http->get("/2010-04-01/Accounts/$sid.json");

The response

The response is a 3-tuple of the response code, headers, and body. Just like a Rack or WSGI application.

list($status, $headers, $body) = $response;

The status is a number, headers are an array, and the body is a string.

if ($status == 200 && $headers['Content-Type'] == 'application/json') {
  $account = json_decode($body);
  var_dump($account->friendly_name);
}

POST example

$http->post(
  "/2010-04-01/Accounts/$sid.json",
  array('Content-Type' => 'application/x-www-form-urlencoded'),
  http_build_query(array('friendly_name' => "Benny's World of Liquor"))
)
<?php
/**
* Copyright 2010 Neuman Vong. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
*/
class TinyHttpException extends ErrorException {}
class TinyHttp {
var $user, $pass, $scheme, $host, $port, $debug;
public function __construct($url, $kwargs = array()) {
$parts = parse_url($url);
foreach (get_object_vars($this) as $name => $_) {
$this->$name = isset($parts[$name]) ? $parts[$name]: NULL;
}
$this->debug = isset($kwargs['debug']) ? !!$kwargs['debug'] : NULL;
}
public function __call($name, $args) {
list($res, $req_headers, $req_body) = $args + array(0, array(), '');
$opts = array(
CURLOPT_URL => "$this->scheme://$this->host$res",
CURLOPT_HEADER => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_INFILESIZE => -1,
CURLOPT_POSTFIELDS => NULL,
CURLOPT_TIMEOUT => 60,
);
foreach ($req_headers as $k => $v) $opts[CURLOPT_HTTPHEADER][] = "$k: $v";
if ($this->port) $opts[CURLOPT_PORT] = $this->port;
if ($this->debug) $opts[CURLINFO_HEADER_OUT] = TRUE;
if ($this->user && $this->pass) $opts[CURLOPT_USERPWD] = "$this->user:$this->pass";
switch ($name) {
case 'get':
$opts[CURLOPT_HTTPGET] = TRUE;
break;
case 'post':
$opts[CURLOPT_POST] = TRUE;
$opts[CURLOPT_POSTFIELDS] = $req_body;
break;
case 'put':
$opts[CURLOPT_PUT] = TRUE;
if (strlen($req_body)) {
if ($buf = fopen('php://memory', 'w+')) {
fwrite($buf, $req_body);
fseek($buf, 0);
$opts[CURLOPT_INFILE] = $buf;
$opts[CURLOPT_INFILESIZE] = strlen($req_body);
} else throw new TinyHttpException('unable to open temporary file');
}
break;
case 'head':
$opts[CURLOPT_NOBODY] = TRUE;
break;
default:
$opts[CURLOPT_CUSTOMREQUEST] = strtoupper($name);
break;
}
try {
if ($curl = curl_init()) {
if (curl_setopt_array($curl, $opts)) {
if ($response = curl_exec($curl)) {
$parts = explode("\r\n\r\n", $response, 3);
list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue')
? array($parts[1], $parts[2])
: array($parts[0], $parts[1]);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($this->debug) {
error_log(
curl_getinfo($curl, CURLINFO_HEADER_OUT) .
$req_body
);
}
$header_lines = explode("\r\n", $head);
array_shift($header_lines);
foreach ($header_lines as $line) {
list($key, $value) = explode(":", $line, 2);
$headers[$key] = trim($value);
}
curl_close($curl);
if (isset($buf) && is_resource($buf)) fclose($buf);
return array($status, $headers, $body);
} else throw new TinyHttpException(curl_error($curl));
} else throw new TinyHttpException(curl_error($curl));
} else throw new TinyHttpException('unable to initialize cURL');
} catch (ErrorException $e) {
if (is_resource($curl)) curl_close($curl);
if (isset($buf) && is_resource($buf)) fclose($buf);
throw $e;
}
}
}
@andy-cline
Copy link

I found a bug when parsing XML with CDATA and multiple line-breaks. In that case, your "explode" will push the XML beyond the line breaks into inaccessible indices in $parts. "explode" is used naively here. So, we want to split the string instead of exploding it. I was able to fix this by replacing lines 72-75 in your code with the following lines:

$head = null;
$body = $response;
$sep = (strpos($response, "\r\n\r\n") === false ? "\n\n" : "\r\n\r\n");

while ((empty($head) || strcasecmp($head, 'HTTP/1.1 100 Continue') == 0) && ($pos = strpos($body, $sep)) !== false) {
$head = substr($body, 0, $pos);
$body = substr($body, $pos+strlen($sep));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment