近来出现了很多REST的API,配合JSONP一起,很容易开发出结构清晰的应用。
我们最近在研究CouchDB。 它有一整套的REST API。
人们已经开发了很多php库,甚至PHP培训(www.tarenaphp.com)扩展来调用CouchDB。
我最喜欢Zend Framework里面的REST client。
但是为了练习一下,我决定自己写一个HTTP client。
我的目的是:创建一个简单的class,可以向远程服务器发送GET ,POST, DELETE请求。
虽然还没有开始进行设计与编码,但我想,我的class应该这样被调用:
echo Http::connect('localhost', 8082)
->doGet('tests/httpclient/dummy.php', array('a' => "a a a"));
PS:这应该算是TDD吧,测试驱动开发。
首先我要实现connect函数,在这个函数里面我不想真的去连接服务器。
应该是在doGet的时候再去连接比较合适。
就像lftp一样,当你在命令行输入
[[email protected] tmp]# lftp 192.168.0.100
lftp 192.168.0.100:~>user uuu ppp
这时其实没有进行真的连接。
只有在你上传/下载/列表文件的时候,才会进行第一次连接。
回到正题,我把connect作成了一个factory函数,它只是保存一下配置信息,
然后返回一个本类的实例。
private $_host = null; private $_port = null; private $_user = null; private $_pass = null;
/**
* Factory of the class. Lazy connect
*
* @param string $host
* @param integer $port
* @param string $user
* @param string $pass
* @return Http
*/ static public function connect($host, $port, $user=null, $pass=null) {
return new self($host, $port, $user, $pass); } protected function __construct($host, $port, $user, $pass) {
$this->_host = $host;
$this->_port = $port;
$this->_user = $user;
$this->_pass = $pass; }
下面为每种操作方式写函数(doGet,doPost,doDelete)
const POST = 'POST'; const GET = 'GET'; const DELETE = 'DELETE';
/**
* POST request
*
* @param string $url
* @param array $params
* @return string
*/ public function doPost($url, $params=array()) {
return $this->_exec(self::POST, $this->_url($url), $params); }
/**
* GET Request
*
* @param string $url
* @param array $params
* @return string
*/ public function doGet($url, $params=array()) {
return $this->_exec(self::GET, $this->_url($url), $params); }
/**
* DELETE Request
*
* @param string $url
* @param array $params
* @return string
*/ public function doDelete($url, $params=array()) {
return $this->_exec(self::DELETE, $this->_url($url), $params); }
它们只是去调用了 _exec()函数。
再加入一个设置Header的函数:
private $_headers = array(); /**
* setHeaders
*
* @param array $headers
* @return Http
*/ public function setHeaders($headers) {
$this->_headers = $headers;
return $this; }
每个函数都返回了这个类的实例,是因为我比较喜欢链式写法。
就像下面这样:
echo Http::connect('localhost', 8082)
->setHeaders($myCustomHeaders)
->doGet('tests/httpclient/dummy.php', array('a' => "a a a"));
最后来看 _exec()函数。在PHP培训里面,有很多方法来实现它的功能,
在这里我用了CURL。 请看一下phpinfo(),确认你的php支持不支持curl。
不支持的时候,您可以使用socket函数。
const HTTP_OK = 200; const HTTP_CREATED = 201; const HTTP_ACEPTED = 202;
/**
* Performing the real request
*
* @param string $type
* @param string $url
* @param array $params
* @return string
*/ private function _exec($type, $url, $params = array()) {
$headers = $this->_headers;
$s = curl_init();
if(!is_null($this->_user)){
curl_setopt($s, CURLOPT_USERPWD, $this->_user.':'.$this->_pass);
}
switch ($type) {
case self::DELETE:
curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params));
curl_setopt($s, CURLOPT_CUSTOMREQUEST, self::DELETE);
break;
case self::POST:
curl_setopt($s, CURLOPT_URL, $url);
curl_setopt($s, CURLOPT_POST, true);
curl_setopt($s, CURLOPT_POSTFIELDS, $params);
break;
case self::GET:
curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params));
break;
}
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
$_out = curl_exec($s);
$status = curl_getinfo($s, CURLINFO_HTTP_CODE);
curl_close($s);
switch ($status) {
case self::HTTP_OK:
case self::HTTP_CREATED:
case self::HTTP_ACEPTED:
$out = $_out;
break;
default:
throw new Http_Exception("http error: {$status}", $status);
}
return $out; }
恩,这里使用了一个 Http_Exception 的异常处理类。
class Http_Exception extends Exception{
const NOT_MODIFIED = 304;
const BAD_REQUEST = 400;
const NOT_FOUND = 404;
const NOT_ALOWED = 405;
const CONFLICT = 409;
const PRECONDITION_FAILED = 412;
const INTERNAL_ERROR = 500; }
在向远程CouchDB服务器请求的过程中,如果出错,CouchDB会返回标准的HTTP STATUS CODE。所以上面的状态码,您应该是蛮熟悉的才对。
异常捕捉代码:
try {
echo Http::connect('localhost', 8082)
->doGet('tests/couchdb/a.php', array('a' => "a a a")); } catch (Http_Exception $e) {
switch ($e->getCode()) {
case Http_Exception::INTERNAL_ERROR:
// do something
break;
} }