创建以 API 为中心的 Web 应用 已翻译 100%

zdhxiong 投递于 2013/08/03 22:18 (共 15 段, 翻译完成于 08-15)
阅读 23883
收藏 391
34
加载中

正计划着要开始搞一个新的网络应用?在这篇教程中,我们将讨论如何创建以API为中心的网络应用,还会解释在今天的多平台世界,这类应用为什么是重要的。

引言

API?

对于还不甚熟悉这个术语的朋友,API是Application Programming Interface(应用编程接口)的简称。根据维基百科

API是以源代码为基础的约定,它用于软件组件之间相互通信的接口。API可能包含函数、数据结构、对象类、以及变量等的约定。

API可视化

图片蒙惠http://blog.zoho.com

简单地讲,API指的是一组应用中的函数,它们能够被其它应用(或者这些函数所属应用自己,下文中我们将会看到)用来与应用进行交互。API是一种很棒的向外部应用安全和妥善地表明其功能的方式,因为这些外部应用所能利用的所有功能仅限于API中所表现出的功能。

stoneyang
stoneyang
翻译于 2013/08/13 20:22
13

什么是“以API为中心的”网络应用?

以API为中心的网络应用就是基本上通过API调用执行大多数甚或所有功能的一类网络应用。

以API为中心的网络应用就是基本上通过API调用执行大多数甚或所有功能的一类网络应用。举个例子,如果你正要登录一个用户,你应当将其认证信息发送给API,然后API会向你返回一个结果,说明该用户是否提供了正确的用户名-密码组合。

以API为中心的网络应用的另外一个特征就是API一直是无状态的,这意味着这种应用无法辨别由会话发起的API调用。由于API调用通常由后端代码构成,实现对会话的掌控将比较困难,因为这其中通常没有cookies介入。这种局限事实上是好事——它“迫使”开发者建造不基于当前用户状态工作的API,但是相应地在功能上,它使测试易于进行,因为用户的当前状态无需被重建。

stoneyang
stoneyang
翻译于 2013/08/13 20:58
7

为什么要经历这些麻烦?

作为Web开发者,我们已经亲眼目睹了技术的进步。有一个常识是,当代的人们不会只通过浏览器来使用应用,还会通过其它诸如移动电话和平板电脑之类的设备使用。举个例子,这篇发表在Mashable上的名为“用户在移动应用上花的时间比在网络上的多”的写道:

一项新近的报告表明,用户花在移动应用上的时间首次超过了花在网络上的时间。

Flurry对比了其移动数据与来自comScore和Alexa的统计数据,发现在六月,用户每天花费81分钟使用移动应用,而只花74分钟用于网上冲浪。

这里还有一篇来自ReadWriteWeb的更新的文章“在移动设备上浏览网络的人多于使用IE6和IE7的人数总和”

来自Sitepoint的 浏览器趋势的最新数据表明,在智能手机上浏览Web的人比使用IE6和IE7浏览的人更多。这两件难有起色的老古董多年来一直是Web开发者的噩梦,它们需要各网站尽可能妥当地降格到至少常用浏览器所能支持的水平。但是现在时代不同了;2011年十一月中,6.95%的Web活动在移动浏览器上发生,而发生在IE6或IE7上的则只有6.49%。

正如我们所见,越来越多的人正通过其它途径获得讯息,特别是移动设备。

stoneyang
stoneyang
翻译于 2013/08/13 21:24
4

这与我创建以API为中心的网络应用有何关系?

这必将会使我们的应用更加有用,因为它可以用在任何你需要的地方。

创建以API为中心的网络应用的主要优势之一便是它帮助你建立可以用于任何设备的功能,浏览器、移动电话、甚至是桌面应用。你所需要做的就是创建的API能够使所有这些设备利用它完成通信,然后,瞧!你将能够建造一个集中式应用,它能够接受来自用户所使用的任何设备的输入并执行相应的功能。

以API为中心的应用的框图

stoneyang
stoneyang
翻译于 2013/08/14 10:02
5

通过以这种方式创建应用,我们能够从容地利用不同的人使用不同的媒介这一优势。这必将使应用更加有用,因为它能用在用户需要的任何地方。

为了证明我们的观点,这里有一篇关于Twitter的重新设计的网站的文章,文章告诉我们他们现在如何利用他们的API来驱动Twitter.com的,实质上是使其以API为中心:

最重要的架构改动之一就是Twitter.com现在是我们自己API的客户。它从终端提取数据,此终端与移动网站,我们为iPhone、iPad、Android,以及所有第三方应用所用端点相同。这一转变使我们能向API团队分配更多的资源,同时生成了40多个补丁。在初始页面负载和来自客户端的每个调用上,所有的数据现在都是从一个高度优化的JSON段缓存中获取的。

在本篇教程中,我们将创建一个简单的TODO列表应用,该应用以API为中心;还要创建一个浏览器上的前端客户端,该客户端与我们的TODO列表应用进行交互。文末,你就能了解一个以API为中心的应用的有机组成部分,同时,还能了解怎样使应用和客户端两者之间的安全通信变得容易。记住这些,我们开始吧!

stoneyang
stoneyang
翻译于 2013/08/14 10:24
5

步骤 1: 规划该应用的功能

本教程中我们将要构建的这个 TODO 应用将会有下面几个基本的CRUD功能:

  • 创建 TODO 条目
  • 读取 TODO 条目
  • 更新 TODO 条目 (重命名,标记为完成,标记为未完成)
  • 删除 TODO 条目

每一个 TODO 条目将拥有:

  • 一个标题 Title
  • 一个截止日期 Date Due
  • 一个描述 Description
  • 一个判断 TODO 条目是否完成的标志 Is Done

让我们模拟一下该应用,使我们考虑该应用以后会是什么样子时,能有有一个直观的参考:

SimpleTODO Mockup

简单的TODO 模拟示例
lwei
lwei
翻译于 2013/08/14 17:26
6

步骤 2: 创建API服务器

既然我们是在开发一个以API为中心的应用,我们将创建两个“项目”: API 服务器,和前端客户端。 我们首先从创建API服务器开始。

在你的web server文件夹,创建一个文件夹,命名为simpletodo_api,然后创建一个index.php文件。这个index.php文件将作为一个访问API的前端控制器,所以,所有访问API服务器的请求都会由该文件产生。打开它并往里输入下列代码:

<?php
// 定义数据目录的路径
define('DATA_PATH', realpath(dirname(__FILE__).'/data'));

//引入我们的models
include_once 'models/TodoItem.php';

//在一个try-catch块中包含所有代码,来捕获所有可能的异常!
try {
	//获得在POST/GET request中的所有参数
	$params = $_REQUEST;
	
	//获取controller并把它正确的格式化使得第一个字母总是大写的
	$controller = ucfirst(strtolower($params['controller']));
	
	//获取action并把它正确的格式化,使它所有的字母都是小写的,并追加一个'Action'
	$action = strtolower($params['action']).'Action';

	//检查controller是否存在。如果不存在,抛出异常
	if( file_exists("controllers/{$controller}.php") ) {
		include_once "controllers/{$controller}.php";
	} else {
		throw new Exception('Controller is invalid.');
	}
	
	//创建一个新的controller实例,并把从request中获取的参数传给它
	$controller = new $controller($params);
	
	//检查controller中是否存在action。如果不存在,抛出异常。
	if( method_exists($controller, $action) === false ) {
		throw new Exception('Action is invalid.');
	}
	
	//执行action
	$result['data'] = $controller->$action();
	$result['success'] = true;
	
} catch( Exception $e ) {
	//捕获任何一次样并且报告问题
	$result = array();
	$result['success'] = false;
	$result['errormsg'] = $e->getMessage();
}

//回显调用API的结果
echo json_encode($result);
exit();

实质上,这里我们创建的是一个简单的前端控制器,它实现了下列功能:

  • 接受一次拥有任意个参数的API调用
  • 为本次API调用抽取出Controller和Action
  • 进行必要的检查确保Controller和Action都存在
  • 执行API调用
  • 捕获异常,如果有的话
  • 返回一个结果给调用者
lwei
lwei
翻译于 2013/08/14 17:52
6
除了需要创建index.php外你还需要创建三个文件夹:  controllers, models 和  data.
API server folders
  • controllers  文件夹存放的是所有我们API服务器将会用到的的控制器。我们用MVC架构来使API服务器结构更清楚合理。
  • models 文件夹存放所有API服务器要用到的数据模型。
  • data 文件夹将会用来保存API服务器的任何数据。

在controllers文件夹下创建一个叫Todo.php的文件。这将是任何TODO列表有关任务的控制器。按照TODO应用所需提供的功能,向Todo控制器里面添加必要的方法:

<?php
class Todo
{
	private $_params;
	
	public function __construct($params)
	{
		$this->_params = $params;
	}
	
	public function createAction()
	{
		//create a new todo item
	}
	
	public function readAction()
	{
		//read all the todo items
	}
	
	public function updateAction()
	{
		//update a todo item
	}
	
	public function deleteAction()
	{
		//delete a todo item
	}
}

现在为每个action中添加必要的功能实现。我将会提供createAction()方法的源码,其他方法将留作作业。如果你觉得毫无头绪,你也可以下载示例的源码,从那里拷贝。

public function createAction()
{
	//create a new todo item
	$todo = new TodoItem();
	$todo->title = $this->_params['title'];
	$todo->description = $this->_params['description'];
	$todo->due_date = $this->_params['due_date'];
	$todo->is_done = 'false';
	
	//pass the user's username and password to authenticate the user
	$todo->save($this->_params['username'], $this->_params['userpass']);
	
	//return the todo item in array format
	return $todo->toArray();
}

bigtiger02
bigtiger02
翻译于 2013/08/15 01:18
5
在文件夹models下创建TodoItem.php,这样我们就可以创建“条目添加”的代码了。注意:我并没有和数据库进行连接,相反我将信息保存到文件中,虽然这可以用任何数据库来实现,但是 这样做相对来说要容易些
<?php
class TodoItem
{
	public $todo_id;
	public $title;
	public $description;
	public $due_date;
	public $is_done;
	
	public function save($username, $userpass)
	{
		//get the username/password hash
		$userhash = sha1("{$username}_{$userpass}");
		if( is_dir(DATA_PATH."/{$userhash}") === false ) {
			mkdir(DATA_PATH."/{$userhash}");
		}
		
		//if the $todo_id isn't set yet, it means we need to create a new todo item
		if( is_null($this->todo_id) || !is_numeric($this->todo_id) ) {
			//the todo id is the current time
			$this->todo_id = time();
		}
		
		//get the array version of this todo item
		$todo_item_array = $this->toArray();
		
		//save the serialized array version into a file
		$success = file_put_contents(DATA_PATH."/{$userhash}/{$this->todo_id}.txt", serialize($todo_item_array));
		
		//if saving was not successful, throw an exception
		if( $success === false ) {
			throw new Exception('Failed to save todo item');
		}
		
		//return the array version
		return $todo_item_array;
	}
	
	public function toArray()
	{
		//return an array version of the todo item
		return array(
			'todo_id' => $this->todo_id,
			'title' => $this->title,
			'description' => $this->description,
			'due_date' => $this->due_date,
			'is_done' => $this->is_done
		);
	}
}

createAction方法使用到TodoItem模型里面两个方法:

  • save() – 该方法将TodoItem保存到一个文件中,如有必要,需要设置todo_id。
  • toArray() – 该方法返回一个以变量为索引的数组Todo条目。

由于API需要通过HTTP请求调用,在浏览器输入如下地址测试API:

http://localhost/simpletodo_api/?controller=todo&action=create&title=test%20title&description=test%20description&due_date=12/08/2011&username=nikko&userpass=test1234

如果没有错,你应该在data文件夹下看到一个新的文件夹,在该文件夹里面有一个文件,文件内容如下:

createAction() result

createAction()结果

恭喜!您已经成功创建了一个的API服务器和API调用!

bigtiger02
bigtiger02
翻译于 2013/08/15 20:37
6

步骤3:确保API服务器具有APP ID和APP SECRET

目前,API服务器被设置为接受全部API请求。我们将需要将之限制在我们自己的应用上,以确保只有我们自己的前端客户端能够完成API请求。另外,你实际上也可以创建一个系统,其中的用户可以创建他们自己的应用,而那些应用也用用对你的API服务器的访问权,这与Facebook和Twitter的应用的的工作原理类似。

我们从为使用API服务器的用户创建一组id-密码对开始。由于这只是一个Demo,我们可以使用任何随机的、32位字符串。对于APP ID,我们将其设定为APP001

再次打开index.php文件,然后用下列代码更新之:

<?php
// Define path to data folder
define('DATA_PATH', realpath(dirname(__FILE__).'/data'));

//Define our id-key pairs
$applications = array(
	'APP001' => '28e336ac6c9423d946ba02d19c6a2632', //randomly generated app key 
);
//include our models
include_once 'models/TodoItem.php';

//wrap the whole thing in a try-catch block to catch any wayward exceptions!
try {
	//*UPDATED*
	//get the encrypted request
	$enc_request = $_REQUEST['enc_request'];
	
	//get the provided app id
	$app_id = $_REQUEST['app_id'];
	
	//check first if the app id exists in the list of applications
	if( !isset($applications[$app_id]) ) {
		throw new Exception('Application does not exist!');
	}
	
	//decrypt the request
	$params = json_decode(trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $applications[$app_id], base64_decode($enc_request), MCRYPT_MODE_ECB)));
	
	//check if the request is valid by checking if it's an array and looking for the controller and action
	if( $params == false || isset($params->controller) == false || isset($params->action) == false ) {
		throw new Exception('Request is not valid');
	}
	
	//cast it into an array
	$params = (array) $params;
	...
	...
	...
stoneyang
stoneyang
翻译于 2013/08/15 13:14
7
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(37)

billow
billow
从概念到实践的好文,👍
小_柒
小_柒
貌似类apicaller.php里面这段“
$result = @json_decode($result);

//check if we're able to json_decode the result correctly
if( $result == false || isset($result['success']) == false ) {
throw new Exception('Request was not correct');
}
”报错“Cannot use object of type stdClass as array” 啊,json_decode()后得到的应该是对象而不是数组,下面$result['success']会出错,应该将$result = @json_decode($result);改成$result = @json_decode($result, true);或者将
if( $result == false || isset($result['success']) == false ) {
改成 if( $result == false || isset($result->success) == false ) {
才对吧?
简单同学
简单同学

引用来自“AnnOS”的评论

靠,和我的框架太相似了,所见略同啊!验证机制,我这边在Api解析层做校验,还可以根据版本使用不同的校验算法。Map映射即可。
可以讲一讲你们的那个API 校验吗?
yiqing95
yiqing95
好文章 不错 !
yusihuo
yusihuo
这样的话,针对html不是每个页面都需要带上用户名和密码了?
m
mopdzz
mark
没头脑的土豆
没头脑的土豆
虽然不搞php,但是文章真的很不错
A
AnnOS
靠,和我的框架太相似了,所见略同啊!验证机制,我这边在Api解析层做校验,还可以根据版本使用不同的校验算法。Map映射即可。
jackyxie
jackyxie
其实现在的MVC开发模式已经有这方面的趋势了,其中异步请求返回JSON串就是文中所讲的。唯一还有差别的就是验证机制(检验自己的应用发来的请求。)
Isronik
Isronik
mark2
返回顶部
顶部