最近API在网络领域有些风靡,明确的说是REST的影响力。这实在没什么好惊讶的,因为在任何编程语言中,消费REST API都是非常的容易。构建它也非常的简单,因为本质上你不会用到任何那些已存在很久的HTTP细则。由于Rails对REST做出的深思熟虑的支持,包括提供和消费这些API(这已经被所有那些和我共事的Rails狂热者阐述过),我要赞美Rails,这样的事情并不常发生。
说真的,如果你从未使用过REST,但是使用过(或者更糟糕的,构建过)SOAP API,或仅仅开过一个WSDL并且将你报价单的头部分解过,伙计,我能有好消息告诉你吗。
那么,REST到底是什么?为什么你应该关注它?
在开始写代码前,我想要确保每个人对REST是什么以及它如何对API有利已经有了较好的认识。首先,从技术上来说,REST并不仅仅针对API,它更像一个通用的概念。然而明显的是,在这篇文章中,我们将在API的语境下谈论REST。所以,让我们来看看API的基本需求以及REST如何作用它们。
请求
所有的API都需要接受请求。有代表性的,对于一个RESTful API,你会拥有一种定义良好的URL模式。让我们假设你想要在你的网站上为用户提供一个API(我知道,我总是为我的例子使用“用户”的概念)。好的,你的URL结构可能类似于“api/users”和“api/users/[id]”这样,这取决于针对你的API被请求的操作类型。你还需要考虑要如何接受数据。目前大多数人正在使用JSON或XML,我个人更倾向于JSON,因为它与JavaScript配合使用更好些,而且PHP拥有简单的功能来编码和解码JSON。如果过去你希望你的API真正的稳健,你能够接受两者通过嗅探出请求的内容类型(例如:application/json 或 application/xml),但是更让人接受的是将内容类型限制成一种。真见鬼,如果你愿意你甚至可以使用简单的键/值对。
请求的另一块内容是它实际上做了什么,比如加载,保存等等。一般的,你不得不想出某种体系架构来定义请求者(消费者)请求的是什么动作,但是REST简化了这些。通过使用HTTP请求方法或动词,我们不需要定义任何东西。我们能够仅仅使用GET,POST,PUT和DELETE方法,这些包含了任何我们需要的请求。你可以将这些动词等价于标准的CRUD风格的东西:GET=加载/检索,POST=创建,PUT=更新,DELETE=delete。注意到这些动词不可以直接对应CRUD是重要的,但是这是一种理解它们的好方法。重新回到上面URL的例子,让我们看一看一些可能的请求意味着什么:
- GET request to /api/users – 列出所有用户
- GET request to /api/users/1 – 列出ID为1的用户信息
- POST request to /api/users – 创建一个新用户
- PUT request to /api/users/1 – 更新ID为1的用户信息
- DELETE request to /api/users/1 – 删除ID为1的用户信息
正如你希望看到的一样,REST通过一些简单,易于理解的标准和协议已经处理了很多在构建API时的主要的棘手问题,但对于一个良好的API还有另一块内容。
响应
因此,REST处理请求非常的容易,但它也容易生成响应。类似于请求,一个RESTful响应有两个主要的部件:响应主体和状态码。响应主体非常容易去处理。像请求一样,大多数REST上的响应或者是JSON文件或者是XML文件(可能在POST情况下仅仅是一个平面文件,这个我们将在之后提到)。同样的,像请求一样,通过另一部分HTTP请求细则—“Accept”,用户能够指定他们想要的响应类型。如果用户希望得到一个XML响应,他们可以仅仅发送一个Accept头信息“Accept: application/xml”作为请求的一部分。无可否认,这个方法没有被广泛的采用,所以你也能在URL中使用扩展的概念。例如,“/api/users.xml”意味着用户想要XML作为响应,类似的,“ /api/users.json”意味着用户要响应JSON(“/api/users/1.json/xml”同理)。无论你选择哪种方式,你都应该选择一种默认的响应类型,因为大多数情况人们甚至都不会告诉你他们想要的。再次声明,我会说选择JSON。如此,没有Accept头或扩展(例如:/api/users)也不应该失败,它应该仅仅以默认的响应类型做“容错”处理。
但是,错误和另外一些重要的与请求相关联的状态信息怎么办呢?这简单,使用HTTP状态码!这已经超过了我对于构建RESTful API的兴趣。通过使用HTTP状态码,你不需要为你的API想出一种“错误/成功”处理模式,这已经被实现了。例如,如果一个用户用POST方法发送“ /api/users”的请求,并且你想要返回一个成功的产物,简单的发送一个201状态码(201=被创造)就可以了。如果失败,发送500状态码(500=内部服务器错误),或者如果搞砸了发送400状态码(400=错误请求)。可能用户尝试用一些不被接受的post去攻击API端点,发送一个501状态码(不被执行)。或许你的MySQL服务器宕机了,因此你的API会被临时性的冻结,发送一个503状态码(服务器不可用)。希望你理解了这个思路。如果你想要阅读更多关于状态码的内容,在wikipedia上查阅它们:List of HTTP Status Codes。
我希望你了解了通过使用REST的概念构建你的API的所有优势。这真的非常的酷,这没有在PHP社区被广泛的讨论是一件耻辱的事(至少就我所讨论到的)。我认为很大部分原因是缺乏关于如何去处理GET或POST以外,也就是PUT和DELETE方法请求的好的文档。不可否认,处理它们确实有些蠢,但是这肯定不困难。我非常确认一些流行的框架里面很可能存在某种形式的REST实现,但我并不是一个狂热的框架迷(基于很多的原因以致于我不想加入),即使有人已经为你实现了解决方案,知道这些对你也是有好处的。
如果你仍然不确信这是一种有用的API范型,看看REST为RoR做了些什么。主要被标榜的一条是构建API将会如何的简单(通过一些RoR狂热者,我确信),事实上也确实如此。对于RoR我知之甚少,但是办公室周围的那些RoR迷多次的给我指出这点。但是,好吧我离题了,让我们写一些代码!
开始使用REST和PHP
一条最终的免责声明:我们将要看到的代码是不可能作为一种稳健的解决方案的例子的。在这里,我的主要目的是向你展示如何在PHP中处理REST的独立部件,将构建最终解决方案的权利留给你。
让我们向深处挖掘!我认为对于我们需要创建一个REST API这件事最好的做一些实际有用的事就是创建一个类,这个类将提供所有的工具函数。我们也会创建一个小类用来储存我们的数据。你也可以拿走它扩展它和在自己的需求中使用它。让我们贴一些代码:
class RestUtils { public static function processRequest() { } public static function sendResponse($status = 200, $body = '', $content_type = 'text/html') { } public static function getStatusCodeMessage($status) { // these could be stored in a .ini file and loaded // via parse_ini_file()... however, this will suffice // for an example $codes = Array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' ); return (isset($codes[$status])) ? $codes[$status] : ''; } } class RestRequest { private $request_vars; private $data; private $http_accept; private $method; public function __construct() { $this->request_vars = array(); $this->data = ''; $this->http_accept = (strpos($_SERVER['HTTP_ACCEPT'], 'json')) ? 'json' : 'xml'; $this->method = 'get'; } public function setData($data) { $this->data = $data; } public function setMethod($method) { $this->method = $method; } public function setRequestVars($request_vars) { $this->request_vars = $request_vars; } public function getData() { return $this->data; } public function getMethod() { return $this->method; } public function getHttpAccept() { return $this->http_accept; } public function getRequestVars() { return $this->request_vars; } }
好的,我们已经得到了一个用来保存一些关于请求(REST请求)信息的简单类,我们可以利用这个类中的一些静态方法去处理请求和响应。正如你看到的,我们确实仅有两个方法要写。这是整件事情最美的地方!好的,让我们继续。
处理请求
处理请求是相当直接的,但是在这里我们能遇到一些猎物(即:PUT和DELETE等,多数是PUT)。我们将在某个时刻重温他们,但现在让我们检查下RestRequest类。如果你注意到了构造函数,你就会看到我们已经解释了HTTP_ACCEPT头部,如果没被提供,将默认为JSON。有了这样的方式,我们只需要处理传入的数据。
我们有很多的方式去做这个,但是假设我们总是会在请求中得到一个键值对:‘数据’=> 实际的数据。同样假设实际的数据是JSON。在我之前对REST的解释中,你能够看到请求的内容类型和或者JSON或者XML的处理方式,但是现在我们应该尽量让其简单。因此,我们处理请求的方法最终看起来像这样:
public static function processRequest() { // get our verb $request_method = strtolower($_SERVER['REQUEST_METHOD']); $return_obj = new RestRequest(); // we'll store our data here $data = array(); switch ($request_method) { // gets are easy... case 'get': $data = $_GET; break; // so are posts case 'post': $data = $_POST; break; // here's the tricky bit... case 'put': // basically, we read a string from PHP's special input location, // and then parse it out into an array via parse_str... per the PHP docs: // Parses str as if it were the query string passed via a URL and sets // variables in the current scope. parse_str(file_get_contents('php://input'), $put_vars); $data = $put_vars; break; } // store the method $return_obj->setMethod($request_method); // set the raw data, so we can access it if needed (there may be // other pieces to your requests) $return_obj->setRequestVars($data); if(isset($data['data'])) { // translate the JSON to an Object for use however you want $return_obj->setData(json_decode($data['data'])); } return $return_obj; }
就像我说的,非常的直接。然而,有些事情要注意。首先,特别的对于DELETE请求不可以接受数据,因此我们在switch中没有对应的case。第二点,你会注意到我们储存了请求变量和解析过的JSON数据这两者。随着你可能有另外的东西作为你的请求的一部分(一个API键或其他什么东西)时这非常有用,这些东西本身并不是真实的数据(像用户的名字,邮箱,等等)。
那么,我们如何使用这个呢?让我们回到用户例子。假设你已经为用户将你的请求路由到正确的控制器了,我们会有一些这样的代码:
$data = RestUtils::processRequest(); switch($data->getMethod) { case 'get': // retrieve a list of users break; case 'post': $user = new User(); $user->setFirstName($data->getData()->first_name); // just for example, this should be done cleaner // and so on... $user->save(); break; // etc, etc, etc... }
请不要在真正的应用程序中这样做,这只是一个应急的例子。你会想把这个封装在一个任何东西都已被合适抽象的很好的控制结构里,但是这个应该帮助你理解了如何使用这个素材。好吧,我离题了,让我们进入到发送响应部分。
发送响应
现在我们能解释请求了,让我们往前到发送响应部分。我们已经知道实际需要做的是发送正确状态码,可能有一些主体(例如,如果是GET请求),但是会有一个重要的捕获对于那些没有主体的响应。假如某人用一个不存在的用户请求攻击我们简单地用户API(如:api/user/123)。在这种情况下,发送404状态码是合适的,但是简单地在头部里发送状态码是不够的。如果在你的浏览器中查看这个页面,你将会看到空白页。这是因为Apache(或者其它运行着的Web服务器)没有发送状态码,所以没有状态页面。我们需要考虑这些当构建我们的方法的时候。记住这些,下面是大致的代码:
public static function sendResponse($status = 200, $body = '', $content_type = 'text/html') { $status_header = 'HTTP/1.1 ' . $status . ' ' . RestUtils::getStatusCodeMessage($status); // set the status header($status_header); // set the content type header('Content-type: ' . $content_type); // pages with body are easy if($body != '') { // send the body echo $body; exit; } // we need to create the body if none is passed else { // create some body messages $message = ''; // this is purely optional, but makes the pages a little nicer to read // for your users. Since you won't likely send a lot of different status codes, // this also shouldn't be too ponderous to maintain switch($status) { case 401: $message = 'You must be authorized to view this page.'; break; case 404: $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.'; break; case 500: $message = 'The server encountered an error processing your request.'; break; case 501: $message = 'The requested method is not implemented.'; break; } // servers don't always have a signature turned on (this is an apache directive "ServerSignature On") $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE']; // this should be templatized in a real-world solution $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>' . $status . ' ' . RestUtils::getStatusCodeMessage($status) . '</title> </head> <body> <h1>' . RestUtils::getStatusCodeMessage($status) . '</h1> <p>' . $message . '</p> <hr /> <address>' . $signature . '</address> </body> </html>'; echo $body; exit; } }
这就是了!在技术上我们有了处理请求和发送响应的一切需要的东西。让我们更多的谈谈为什么我们需要一个标准的或自定义的响应主体。对于GET请求来说,这是非常明显的,我们需要发送“XML/JSON”内容取代状态页面(只要请求是有效的)。然而,还有POST要处理。在你的应用程序里,当你创建一个新实体,你可能会通过类似mysql_insert_id()函数这样的方法来获取新实体的ID。如果一个用户向你的API发送一个POST请求,他们同样想要一个新ID。我通常处理这种情况的方法是简单的发送一个作为主体的新的ID(伴随一个201状态码),但是如果你愿意你可以将他们封装在XML或JSON中。
让我们稍微来扩展一下我们的简单应用:
switch($data->getMethod) { // this is a request for all users, not one in particular case 'get': $user_list = getUserList(); // assume this returns an array if($data->getHttpAccept == 'json') { RestUtils::sendResponse(200, json_encode($user_list), 'application/json'); } else if ($data->getHttpAccept == 'xml') { // using the XML_SERIALIZER Pear Package $options = array ( 'indent' => ' ', 'addDecl' => false, 'rootName' => $fc->getAction(), XML_SERIALIZER_OPTION_RETURN_RESULT => true ); $serializer = new XML_Serializer($options); RestUtils::sendResponse(200, $serializer->serialize($user_list), 'application/xml'); } break; // new user create case 'post': $user = new User(); $user->setFirstName($data->getData()->first_name); // just for example, this should be done cleaner // and so on... $user->save(); // just send the new ID as the body RestUtils::sendResponse(201, $user->getId()); break; }
重申一下,这仅仅是一个例子,但是它展示了(我认为,至少是这样的)实现RESTful所要做出的努力。
总结
这就是了!我非常自信已经把一些观点易于理解的指了出来,因此我愿意接受你如何更进一步的领悟这个材料,而且或许可以正确的实现它。
在现实的MVC应用程序中,你或许想做的是为你的加载个别API控制器的API设置一个控制器。例如,使用上面的原型,我们可能创建一个包含get(),put(),post()和delete()方法的UserRestController控制器。这些方法将会使用工具来处理请求,智能的做一些需要做的事,然后使用工具发送响应。
你也可以更进一步,抽象出你的API控制器和数据模型。不用在你的应用程序中为每个数据模型创建一个控制器,你可以添加一些逻辑到你的API控制器并且首先寻找一个显示定义的控制器,如果没找到,则尝试去寻找一个存在的模型。例如,URL“api/user/1”将会首先查找一个“user”的rest控制器,如果没找到,再在你的应用程序中寻找一个叫做“user”的模型。如果找到了一个,你可以对这些模型写一些自动化巫师程序来自动化处理所有的请求。
更进一步,你可以创建一个一般的“list-all”方法,其工作原理类似于先前段落的例子。假设你的url是“api/users”。API控制器将会首先检查“users”rest控制器,如果没找到,识别用户被多元化,解除多元化,然后查找“user”模型。如果发现一个,加载一个列表的用户列表并发出。
最后,你可以同样简单的为你的API加上摘要式身份验证。假设你只想要合适认证的用户有访问你API的权限,你可以向这样在你的处理请求的功能在加入一些代码(借用我的现有应用,有一些常量和变量引用没有被定义在这个片段中)。
// figure out if we need to challenge the user if(empty($_SERVER['PHP_AUTH_DIGEST'])) { header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="' . AUTH_REALM . '",qop="auth",nonce="' . uniqid() . '",opaque="' . md5(AUTH_REALM) . '"'); // show the error if they hit cancel die(RestControllerLib::error(401, true)); } // now, analayze the PHP_AUTH_DIGEST var if(!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || $auth_username != $data['username']) { // show the error due to bad auth die(RestUtils::sendResponse(401)); } // so far, everything's good, let's now check the response a bit more... $A1 = md5($data['username'] . ':' . AUTH_REALM . ':' . $auth_pass); $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); $valid_response = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); // last check.. if($data['response'] != $valid_response) { die(RestUtils::sendResponse(401)); }
非常酷的原型,是吗?通过一点点代码和一些聪明的逻辑,你可以非常快捷的在你的应用程序中加入一个全功能的REST API。我并不仅仅是在鼓励这个概念,我花了半天时间在我的个人框架中实现了它,又花了另一个半天在里面加入了各式各样的魔法。如果你对我的最终实现感兴趣,在评论中注明,我将非常高兴地与你分享。如果你有任何愿意分享的酷的点子,同样也请在评论中写下它们。如果我足够喜欢它,我甚至乐意让您自己创作关于这一主题的文章。
下次再见。
原文链接:Create a REST API with PHP
(完)
相关推荐
通过创建这样一个插件,我们能够使REST API更好地服务于现代Web开发的需求,为那些依赖API获取数据的前端应用提供更加丰富的文章信息,从而提高用户体验。此外,这也展示了WordPress插件开发的灵活性,以及开发者...
标题"Advanced PHP5 REST API for Shodan" 提示我们这是一个使用PHP5构建的高级RESTful API,专门用于与Shodan服务进行交互。Shodan是一个知名的互联网搜索引擎,它允许用户搜索全球范围内的互联网设备、服务器、...
在本文中,我们将深入探讨如何在uniapp项目中利用unipush和个推SDK服务端实现推送功能,并结合Thinkphp + RestAPI V2构建后端系统。uniapp是一款跨平台的移动应用开发框架,它允许开发者用一套代码库来创建iOS、...
这个"REST api demo"项目提供了学习和实践REST API设计和实现的一个基础平台,对于理解如何将这些技术结合使用以构建现代Web服务非常有帮助。通过深入研究和扩展这个项目,开发者可以更好地掌握RESTful API设计原则...
【PHP-CRUD-API v2】是一个轻量级的PHP脚本,专为快速搭建与SQL数据库交互的RESTful API而设计。这个单一文件的解决方案极大地简化了开发过程,允许开发者通过HTTP请求来执行CRUD(创建、读取、更新和删除)操作,...
Laravel,一个优雅的 PHP 框架,为开发者提供了构建高质量 RESTful API 的强大工具。本教程将探讨如何在 Laravel 项目中轻松实现 RESTful API 的开发。 ### 1. 安装 Laravel 首先,确保您已经安装了 Laravel ...
RetrofitPHP是一个专门为PHP开发者设计的库,用于简化REST API客户端的创建。REST(Representational State Transfer)是一种架构风格,广泛应用于Web服务的设计,通过HTTP协议进行数据交换。Retrofit库将这种风格与...
一旦安装完成,API Platform会自动配置并提供一个预定义的/api路由,同时会生成一个OpenAPI文档页面,方便开发者了解和使用API。 在后续的开发中,例如创建文章类的接口,可以利用API Platform提供的工具和注解来...
总结,创建一个使用PHP和MySQL的REST API涉及数据库连接、设计API端点、处理HTTP请求、数据操作以及错误处理等多个步骤。遵循RESTful原则,你的API将变得易于理解和维护,为客户端提供高效的服务。
WordPress REST API(Representational State Transfer Application Programming Interface)是WordPress平台中的一个重要组成部分,它允许开发者通过HTTP协议与WordPress站点进行交互,获取或修改数据,极大地增强...
【个推RestAPI V2】C#服务端SDK是一个针对Android设备推送服务的开发工具,主要功能是通过C#编程语言实现对个推平台提供的RestAPI V2接口的调用,以便于开发者进行消息推送、用户管理、统计分析等操作。个推是一家...
Symfony 4 JWT REST API示例/样板文件/演示 这是使用JWT(JSON Web令牌)的Symfony 4 REST API的样板实现(在类固醇上)。 它是在考虑最佳REST API惯例的情况下... 这是一个简单的橄榄球队和联赛管理应用程序,以REST A
2º在项目根目录中创建一个.env文件,并设置环境变量(在.env.example中查看所需的env变量)。 3º运行init.sh文件以启动服务。 其余API端点 GET / user->返回所有用户信息。 GET / user / {id}->获得单个用户信息...
**php-activiti-api** 是一个专门为PHP开发者设计的库,它允许用户通过RESTful API与Activiti工作流引擎进行交互。Activiti是一款开源的工作流和业务自动化引擎,广泛应用于企业的业务流程管理(BPM)系统。这个...
本教程将深入探讨如何使用 Laravel 实现一个 RESTAPI,主要涉及以下几个核心知识点: 1. **安装与配置 Laravel** - 首先,确保您的系统已经安装了 PHP 和 Composer。通过 Composer 安装 Laravel:`composer create...
PSX框架 关于 PSX是用PHP编写的专用于构建REST API的框架。 它基于涵盖API生命周期许多方面的多个组件。...要安装完整堆栈框架,您可以安装示例项目,该示例项目使用示例API创建一个基本PSX项目。 php compose
在开发人员部分中创建一个新的access_key PHP的MessageBird API客户端需要PHP> = 7.0。 安装 作曲家的安装 运行composer require messagebird/php-rest-api 。 手动安装 不使用Composer时。 您可以git checkout或...
例如,假设我们要创建一个管理用户资源的 API。我们可以定义以下路由: ```php <?php // 获取所有用户 $app->get('/users', function ($request, $response) { // 查询数据库并返回用户列表 }); // 创建新用户 $...
在标签中,"PHP开发-REST API"表明 Drest 是专门针对 PHP 开发者在构建 REST API 时使用的一个工具。这暗示了使用 Drest 可以帮助 PHP 开发人员更快速地构建符合 REST 风格的 API,同时充分利用 Doctrine ORM 提供的...
而"rest-api-helper"则是专门为Laravel设计的一个扩展包,旨在帮助开发者快速设置和管理RESTful API。 首先,我们要理解REST(Representational State Transfer)架构风格,它是Web服务设计的一种最佳实践。RESTful...