综合学过的知识
到目前为止,您已经:
- 了解如何通过 PHP 与 CICS COMMAREA 程序交互
- 了解一些能够帮助构建 REST 式 Web 服务的 CA1S 约定和函数
我们将使用这些知识将书库程序公开为 REST 式 Web 服务。
REST 式 设计
资源
第一步是定义我们希望公开的资源。在这个场景中,我们使用惟一的资源类型 “书”,它有 4 个属性:
book {
string: title, // The title of the book
string: author, // The author of the book
boolean: onLoan, // Whether the book is on loan
string: borrower // The person who borrowed the book
// if it is on loan, null otherwise.
}
|
方法
接下来,我们简单描述每个受支持方法的输入/输出数据:
表 6. 集合 URI (/ca1s/resources/book) 上的请求
|
HTTP 方法
|
在 book.php 中调用的处理程序方法
|
描述
|
GET
|
book::onList()
| 返回用 JSON 编码的图书及其所有属性的列表。 |
POST
|
book::onCreate()
| 向书库添加一本书。 这个请求必须提供用 JSON 编码的新书属性 title 和 author。这将返回一条消息,表明书已添加。 |
PUT
|
book::onPutCollection()
| 不支持 |
DELETE
|
book::onDeleteCollection()
| 不支持 |
表 7. 成员 URI (/ca1s/resources/book/10) 上的请求
|
HTTP 方法
|
在 book.php 中调用的处理程序方法
|
描述
|
GET
|
book::onRetrieve()
| 返回使用 JSON 编码的书及其所有属性。如果要请求某个属性(例如,book/10/title),将仅返回该属性(用 JSON 编码)。 |
POST
|
book::onPostMember()
| 不支持 |
PUT
|
book::onUpdate()
| 将图书标记为已借出或已归还。这个请求必须提供一个包含 onLoan 和 borrower 属性的 JSON 对象。不支持更新书的作者和标题。这将返回一条消息,表明书已添加。 |
DELETE
|
book::onDelete()
| 从书库中将书永久删除。这将返回一条消息,表明书已删除。 |
任何错误都将导致服务返回一个相应的 HTTP 状态码,以及在 JSON 对象属性 errorMessage 中返回一条错误消息。例如,尝试获取 ID 为 909 的图书时,如果不存在这本书的话,将返回 HTTP 404 响应,其内容体为:
{"errorMessage":"Could not retrieve book 909 as it was not found."}
|
实现
最后,让我们根据以上的设计实现资源处理程序 book.php。resources/book.php 中的示例代码包包含了完整的脚本。
对所有事件通用的代码
脚本 book.php 包含的 book 类带有 LCRUD 处理程序方法,同时还包含除类定义之外的代码。在调用恰当的处理程序方法之前,将对所有请求执行这个代码。
在我们使用的场景中,在类定义之外导入表示 COMMAREA 和程序常量的 Java 类非常重要,因为它们是最常用的。我们还使用 PHP function header() 将响应内容类型设置为 text/json,因为所有数据都以 JSON 格式返回:
清单 7. 所有请求类型都使用的引导代码
// Load the Java classes
java_import('library.Library_Commarea');
java_import('library.Library_Constants');
// All responses will be JSON, so always set response content-type to text/json.
header('Content-Type: text/json');
|
我们还在一个全局函数中打包程序链接操作及其异常处理。这个函数对所有事件处理程序都是可用的。如果书库程序意外终止,脚本的执行将暂停,并且向客户端返回一条错误消息。
清单 8. 程序链接的包装器函数
/**
* Invoke the library program with the supplied COMMAREA.
* Notify the client if an error occurs.
*/
function runLibraryProgram($COMMAREA) {
$program = new CICSProgram('LIBRARY');
try {
$program->link($COMMAREA);
} catch (CICSException $e) {
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] =
'Exeption when linking with CICS program:' . $e->getMessage();
echo json_encode($error);
exit;
}
}
|
最后,在调用任何处理程序之前调用类构造器,因此我们可以使用它设置所有方法都可以使用的属性。
清单 9. 图书资源处理程序构造器
/**
* The constructor is invoked before event handlers.
*/
function __construct() {
$this->COMMAREA = new Library_Commarea();
$this->constants = new Library_Constants();
}
|
接下来,我们看看每个事件处理程序的实现。
onList
onList() 处理程序实现类似于 library.php 脚本(我们使用它了解如何从 PHP 调用 COMMAREA)的逻辑:它使用 LIST 命令调用书库程序,然后遍历图书集合。然而,这个版本不仅输出图书的标题和作者,而且还构建一个表示图书的关联数组,然后将其序列化为 JSON。它还检查调用书库返回的代码,如果出现问题,将向客户端发送一条错误消息。
清单 10. onList 处理程序
/**
* Respond with a JSON-encoded list of books.
*/
function onList() {
// Attempt to get book list
$this->COMMAREA->setLibRequestType('LIST');
runLibraryProgram($this->COMMAREA);
// Process return code
switch ($this->COMMAREA->getLibReturnCode()) {
case $this->constants->getLibraryOk():
// Return the list of books
$books = array();
for ($i = 0; $i<$this->COMMAREA->getLibItemCount(); $i++) {
$CICSbook = $this->COMMAREA->getLibBookItem($i);
$bookId = $CICSbook->getBookItemRef();
$books[$bookId]['title'] = trim($CICSbook->getBookTitle());
$books[$bookId]['author'] = trim($CICSbook->getBookAuthor());
$books[$bookId]['onLoan'] = $CICSbook->isBookOnloan();
$books[$bookId]['borrower'] = $books[$bookId]['onLoan'] ?
trim($CICSbook->getBookBorrower()) : null;
}
echo json_encode($books);
break;
default:
// Notify client of internal error
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] = 'Unexpected return code when listing books: '
. $this->COMMAREA->getLibReturnCode();
echo json_encode($error);
}
}
|
onCreate onCreate() 处理程序首先从请求获取输入数据并验证该数据,然后采用类似于 onList() 的模式。使用 PHP 函数 header()(见 参考资料)在请求成功后设置恰当的 HTTP 响应代码。
清单 11. onCreate 处理程序
/**
* Add book to library based on JSON data in the request body.
*/
function onCreate() {
// Check input data for new book
$book = json_decode(zget('/request/input/transcoded'), true);
if (!is_array($book) || !isset($book['title'], $book['author'])) {
header('HTTP/1.1 400 Bad Request');
$error['errorMessage'] = 'Bad book data: ' . zget('/request/input/transcoded')
. '. Please specify an author and a title.';
echo json_encode($error);
return;
}
// Attempt to create book
$this->COMMAREA->setLibRequestType('ADD');
$this->COMMAREA->getLibBookItem(0)->setBookTitle($book['title']);
$this->COMMAREA->getLibBookItem(0)->setBookAuthor($book['author']);
runLibraryProgram($this->COMMAREA);
// Process return code
switch ($this->COMMAREA->getLibReturnCode()) {
case $this->constants->getLibraryOk():
header('HTTP/1.1 201 Created');
$bookId = $this->COMMAREA->getLibBookItem(0)-> getBookItemRef();
$status['statusMessage'] = "Successfully created book $bookId.";
echo json_encode($status);
break;
case $this->constants->getLibraryFull():
header('HTTP/1.1 400 Bad Request');
$error['errorMessage'] = 'Could not create book : Library is full.';
echo json_encode($error);
break;
default:
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] = 'Unexpected return code when creating book '
. $this->COMMAREA->getLibReturnCode();
echo json_encode($error);
break;
}
}
|
onRetrieve onRetrieve() 类似于 onList(),但它访问的是一本书,而不是完整的列表。另外,对于子路径的请求(比如 book/10/title),它还包含访问图书的某个属性的逻辑。请求的属性由 zget('/event/pathInfo') 决定。
清单 12. onRetrieve 处理程序
/**
* Respond with a JSON representation of an individual book, or a specific attribute
* of a book.
*/
function onRetrieve() {
$bookId = zget('/request/params/bookId');
$this->COMMAREA->setLibRequestType('QUERY');
$this->COMMAREA->getLibBookItem(0)->setBookItemRef($bookId);
runLibraryProgram($this->COMMAREA);
// Process return code
switch ($this->COMMAREA->getLibReturnCode()) {
case $this->constants->getLibraryOk():
$book['title'] = trim($this->COMMAREA->getLibBookItem(0)->getBookTitle());
$book['author'] = trim($this->COMMAREA->getLibBookItem(0)->getBookAuthor());
$book['onLoan'] = $this->COMMAREA->getLibBookItem(0)->isBookOnloan();
$book['borrower'] = $book['onLoan'] ?
trim($this->COMMAREA->getLibBookItem(0)->getBookBorrower()) : null;
break;
case $this->constants->getLibraryNotfound():
header('HTTP/1.1 404 Not Found');
$error['errorMessage'] = "Could not retrieve book $bookId as it was not found.";
echo json_encode($error);
return;
default:
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] = "Unexpected return code deleting book $bookId: "
. $this->COMMAREA->getLibReturnCode();
echo json_encode($error);
return;
}
// Send back appropriate info about the book
$requestedInfo = zget('/event/pathInfo');
switch($requestedInfo) {
case null:
// return the whole book
echo json_encode($book);
break;
case '/title':
case '/author':
case '/onLoan':
case '/borrower':
// return just the relevant info
$requestedInfo = substr($requestedInfo, 1);
echo json_encode(array($requestedInfo => $book[$requestedInfo]));
break;
default:
header('HTTP/1.1 404 Not Found');
$error['errorMessage'] = "Could not retrieve book detail $requestedInfo
about book $bookId: don't know what $requestedInfo is.";
echo json_encode($error);
}
}
|
onUpdate
onUpdate() 处理程序检查输入数据的正确性,它可能包含 onLoan 和 borrower 属性。然后通过调用恰当的 CICS 程序将图书标记为 “已借出” 或 “已归还”。和前面的处理程序一样,将对程序的返回代码进行检查,并恰当地处理错误。与 OnRetrieve() 不同,这个处理程序不支持对子路径(例如,book/10/onLoan)的单个属性执行操作;如果您想进一步体验,可以放心添加这个函数!
清单 13. onUpdate 处理程序
/**
* Update the status of a book to mark it as borrowed or returned.
*/
function onUpdate() {
// Check input data
$bookId = zget('/request/params/bookId');
$updateData = json_decode(zget('/request/input/transcoded'), true);
if (!isset($updateData['onLoan'])) {
header('HTTP/1.1 400 Bad Request');
$error['errorMessage'] = 'Bad book update data: ' .
zget('/request/input/transcoded') . '. Please specify onLoan.';
echo json_encode($error);
return;
}
if ($updateData['onLoan'] && empty($updateData['borrower'])) {
header('HTTP/1.1 400 Bad Request');
$error['errorMessage'] = 'Bad book update data: ' .
zget('/request/input/transcoded') . '. Please specify a borrower.';
echo json_encode($error);
return;
}
// Attempt to update book status
$this->COMMAREA->setLibRequestType($updateData['onLoan'] ? 'BORROW' : 'RETURN');
$this->COMMAREA->getLibBookItem(0)->setBookItemRef($bookId);
$this->COMMAREA->getLibBookItem(0)->setBookLoanStatus($updateData['onLoan']);
if (isset($updateData['borrower'])) {
$this->COMMAREA->getLibBookItem(0)->setBookBorrower($updateData['borrower']);
}
runLibraryProgram($this->COMMAREA);
// Process return code
switch ($this->COMMAREA->getLibReturnCode()) {
case $this->constants->getLibraryOk():
$status['statusMessage'] = "Successfully updated status of book $bookId.";
echo json_encode($status);
break;
case $this->constants->getLibraryNotfound():
header('HTTP/1.1 404 Not Found');
$error['errorMessage'] = "Could not update book $bookId as it was not found.";
echo json_encode($error);
break;
default:
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] =
"Unexpected return code when updating status of book $bookId: "
. $this->COMMAREA->getLibReturnCode();
echo json_encode($error);
}
}
|
onDelete
最后一个处理程序是 onDelete(),它通过调用 CICS 程序对请求 URI 中的图书 ID 执行 “DELETE” 操作。
清单 14. onDelete 处理程序
/**
* Delete a book from the library.
*/
function onDelete() {
// Attempt to Delete book
$bookId = zget('/request/params/bookId');
$this->COMMAREA->setLibRequestType('DELETE');
$this->COMMAREA->getLibBookItem(0)->setBookItemRef($bookId);
runLibraryProgram($this->COMMAREA);
// Process return code
switch ($this->COMMAREA->getLibReturnCode()) {
case $this->constants->getLibraryOk():
$status['statusMessage'] = "Successfully deleted book $bookId.";
echo json_encode($status);
break;
case $this->constants->getLibraryNotfound():
header('HTTP/1.1 404 Not Found');
$error['errorMessage'] = "Could not delete book $bookId as it was not found.";
echo json_encode($error);
break;
default:
header('HTTP/1.1 500 Internal Server Error');
$error['errorMessage'] = "Unexpected return code deleting book $bookId: "
. $this->COMMAREA->getLibReturnCode();
echo json_encode($error);
}
}
|
使用 Web 服务
现在,这个书库应用程序已经通过一致的接口公开,并且使用明确定义的 JSON 数据结构进行通信,因此,各种客户端都可以轻松访问它。测试该服务的最简单方法是使用轻量级的 REST 客户端,比如 Poster add-on for Firefox(见 参考资料)。
图 5. 使用简单的客户端测试服务
在示例代码中,我们包含了一个向服务发出 Ajax 请求的 HTML 和 JavaScript 页面。如果您要试用它,可以将 library/scripts/ajaxLibrary.php 复制到您的 CICS 系统的 /ca1s/work/scripts/ajaxLibrary.php 目录下(将 resources/book.php 转移到 /ca1s/work/resources/book.php,如果还没有转移的话),然后在浏览器中访问它。
图 6. 服务的 Ajax 前端
可以通过某个平台(比如 WebSphere sMash)将这个书库服务与其他服务进行混搭。
|