在 Drupal 7 中创建自定义 Web 服务项目

充分利用灵活的 Services 模块

Web 服务是一种基于 Web 的软件应用程序,可与其他基于 Web 的应用程序交换数据。Web 服务有助于链接不同网络平台、硬件、软件和数据库上操作的应用程序,将来自不同来源的软件和服务组合起来以提供无缝集成服务。本问将学习如何创建一个自定义 Web 服务,使您能够使用 Services 模块将一个 Drupal 站点的内容显示在另一个站点上。

Timi Ogunjobi, 软件工程师兼作者, Xceedia Limited

Timi Ogunjobi 照片Timi Ogunjobi 是一名作者兼 Web 应用程序开发人员。他是 Xceedia Limited 的 CEO,这是一家致力于 Web 和移动应用程序开发的软件工程公司。他还是 3 部 Drupal 图书的作者。



2012 年 4 月 01 日

Services 模块允许您从一个主要模块后端配置和管理区域启用您 Drupal 站点上自定义构建的内容服务器和服务。该模块中包含的服务允许您调用内容,从 Drupal 的默认和分配的 File、Comment、Search、User、Menu、Node、System、Taxonomy 和 Views 模块输出数据。通过调用这些服务,可从您的 Drupal 站点抓取数据并显示在另一个 Drupal 站点上(无论是在相同的本地服务器上还是来自外部位置)。但是,也可以使用 Services 模块将并未基于 Drupal 的应用程序与外部 Web 服务相集成。

Services 模块的灵活性允许您以编程方式创建自己的自定义服务模块,并将其与主要 Services 模块中封装的方法调用(比如具象状态传输 [REST]、JavaScript 对象表示法 [JSON] 和 XML-远程过程调用 [XML-RPC])相集成。Services 模块的主要优势在于:它支持 Web 服务与多个应用程序集成,同时使用标准 Drupal 模块代码和编程。更重要的是,Services 模块有助于减少编写您自己的 Web 所花的时间量,因为它为许多常见的 Web 服务应用程序环境提供了一个标准接口。

本文探讨如何创建一个 Web 服务模块,它将使用 Service 模块从一个 Drupal 站点运行一个自定义回调。您可编写一个自定义模块以从一种特定内容类型的节点返回一组数据。您还将学习如何:

  • 为服务编写自定义模块
  • 将此模块与 Services 模块相集成,返回输出来进行测试(这是一个返回一组节点的简单回调示例)。

获取和安装需要的模块

首先,您必须从 Drupal.org 站点下载以下必要的模块:

  • Services。这是从项目页面下载的主要 Services 模块(参见 参考资料 获取一个链接)。
  • Chaos 工具套件。Services 模块依赖于这个套件,所以您必须首先实际安装它,或者与 Services 模块同时安装。Chaos 工具套件为端点定义提供了框架,以便可在数据库和代码中定义和导出它们。参见 参考资料 获取下载站点的链接。
  • REST 服务器。您使用 REST 服务器(它包含在 Services 下载中)来测试服务。也可以使用其他服务,比如 JSON 和 XML-RPC。
  • Autoload。REST 服务器需要这个实用程序模块。Autoload 模块使其他模块可以以一种统一方式利用 PHP 5 的 autoloading 类。请参见 参考资料 获得一个链接。

将这些模块安装在您的 /sites/all/modules 文件夹中。上传服务之后,如果您查看文件夹内部,将会发现它包含两个子文件夹:servers 和 services。servers 文件夹包含两个 Web 服务模块代码,核心模块包含对 XML-RPC 服务器的支持。services 文件夹包含提供基于 Drupal 的内容(包括文件、注释、节点、菜单、搜索、用户、系统、分类和视图)的 Web 服务的模块(参见 图 1)。这些文件夹都是 Services 模块的子文件夹。

图 1. Services 模块文件夹的内容
该屏幕截图显示了 Services 模块文件夹的内容

安装完成后,转到您 Drupal 站点中的主要模块管理列表,启用主要的核心 Services 模块、Key Authentication 模块、XML-RPC 服务器和 Services 模块。启用这些模块后,您将能够在 Administration 页面上查看已安装和启用的服务器和服务,以及主要的核心服务设置。从您站点的 /admin/structure/services 页面,您可以轻松浏览已安装的服务器和 Services 模块,查看您添加到站点中供服务器和所有服务使用的任何应用编程接口 (API) 密钥。图 2 给出了一个示例 Browse 页面。

图 2. 添加新服务
该屏幕截图显示了添加新服务的界面,其中突出显示了 +Add 按钮

单击 Add 以列出和命名您的 Web 服务,添加一个允许使用的外部 Web 服务器域(您将与其通信)。接着,您通信的 Web 服务可访问您的站点,从而使用这些服务和访问数据。您可能已经熟悉社交媒体网站(比如 Facebook 和 Twitter)和其他众多 Web 应用程序,这些 Web 应用程序支持无缝交换和导入用户数据,让登录变得很方便。从 Admin > Configuration > Services 部分,Settings 页面显示已启用的身份验证方法和设置,并询问您是否希望在使用您的内容时向其应用其他内容权限。您的内容类型上的字段属性不会在 Web 服务调用期间自动级联。默认情况下,所有内容字段都会返回,无一例外,但您可以应用特定的内容字段级权限。


创建一个自定义服务模块

Drupal 模块 是一组使用 PHP 编写的文件,能够有效地扩展基本应用程序的功能。更仔细地查看一下模块在结构上与任何其他 PHP 文件没什么不同,可以在多个不同的安装中独立创建、测试和使用。模块代码通常可访问所有变量和结构,以及使用 Drupal 核心的所有功能。类似地,Drupal 核心可通过称为挂钩 的固定接口调用这些功能,这些功能在模块中定义来增强它固有的基本功能。

挂钩使 Drupal 能够执行以下任务:

  • 在站点内添加模块定义的新 URL 和页面 (hook_menu)
  • 向 Drupal 页面添加内容(hook_blockhook_footer 等)
  • 创建自定义数据库表 (hook_schema)

在您将创建的新模块的代码中,您将在多个地方看到挂钩的定义。就像大部分其他 Drupal 模块一样,模块文件通常包含 3 个基本文件:modulename.info、modulename.module 和 modulename.inc。一种更复杂的模块可能包含更多文件。

要开始创建自定义服务或 Services 模块文件,在 /sites/all/modules/services 文件夹中,为此模块添加一个名为 note_service 的新文件夹。

.info 文件

除了 note_service 文件夹,创建一个名为 note_service.info 的新文件。此文件将包含所有必要的元数据和信息,Drupal 可通过它们在主要模块管理页面中列出模块。

在 .info 文件中,输入如 清单 1 所示的代码。

清单 1. Note service .info 文件
name = Note Service
description = Services for the Note content type.
package = Note example
dependencies[] = services
files[] = note_service.inc
files[] = note_service.module
core = 7.x

将该文件保存为 note_service.info。此代码给出了服务的名称、它所执行操作的描述、自定义模块所在的整体模块包,以及依赖关系。在本例中,这意味着您的自定义模块要正常工作,必须安装和启用 Services 模块。将这个 note_service.info 文件放在您的模块文件夹中。

.install 文件

.install 文件告诉 Drupal 如何安装此模块(在本例中,通过挂钩模式的实现来安装)。.install 文件在首次启用一个模块时运行,它的基本功能是运行该模块所需的设置过程。.install 文件执行的最常见任务是创建数据库表和字段。安装指令包含在一个 _install() 函数中。这个挂钩函数在最初启用模块时调用。该文件也用于在安装模块的新版本时执行更新。.install 文件没有特殊的语法:它只是一个具有不同文件扩展名的 PHP 文件。清单 2 给出了 note service .install 文件的代码。

清单 2. Note service .install 文件
<?php
// note_service.install
/**
* Implementation of hook_schema().
*/
function note_service_schema() {
  $schema['note'] = array(
    'description' => t('Stores information about notes.'),
    'fields' => array(
      'id' => array(
        'description' => t('The primary identifier for a note.'),
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'uid' => array(
        'description' => t('The user that created the note.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'created' => array(
        'description' => t('The timestamp for when the note was created.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'modified' => array(
        'description' => t('The timestamp for when the note was modified.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'subject' => array(
        'description' => t('The subject of the note'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
      ),
      'note' => array(
        'description' => t('The note'),
        'type' => 'text',
        'size' => 'medium',
      ),
    ),
    'primary key' => array('id'),
  );
  return $schema;
}
?>

定义服务

现在,创建您的模块文件。此文件包含返回数据数组和输出 Note 节点的 PHP 函数。该函数主要实现 Drupal 的 hook_services()。无需深入理解 Drupal 模块开发,但理解它会有所帮助,更不用说 .module 文件中的函数是 PHP 函数,所以一定要确保模块文件代码中存在一个开放的 <?php 标记。

Services 模块的大部分服务实现了创建、检索、更新、删除、索引 (CRUD) 方法,但它也可以通过本文中未介绍的其他方式实现操作、有针对性的操作和关系。清单 3 给出了创建模块文件的代码。

清单 3. Note service .module 文件
<?php
// note_service.module
/** Gets a note object by id.
*
* @param int $id
* @return object
*/
function note_service_get_note($id) {
  return db_query("SELECT * FROM {note} WHERE id='".$id."'")->fetchAll();
}

/** Writes a note to the database
*
* @param object $note
* @return void
*/
function note_service_write_note($note) {
  $primary_key = !empty($note->id) ? array('id') : NULL;
  drupal_write_record('note', $note, $primary_key);
}

/**
* Deletes a note from the database.
*
* @param int $id
* @return void
*/
function note_service_delete_note($id) {
  db_query("DELETE FROM {note} WHERE id='".$id."'");
}

/**
* Implementation of hook_services_services().
*/
function note_service_services_services() {
  return array(
    'note' => array(
      'retrieve' => array(
        'help' => 'Retrieves a note',
        'file' => array('file' => 'inc', 'module' => 'note_service'),
        'callback' => '_note_service_retrieve',
        'access callback' => '_note_service_access',
        'access arguments' => array('view'),
        'access arguments append' => TRUE,
        'args' => array(
          array(
            'name' => 'id',
            'type' => 'int',
            'description' => 'The id of the note to get',
            'source' => array('path' => '0'),
            'optional' => FALSE,
          ),
        ),
      ),
      'create' => array(
        'help' => 'Creates a note',
        'file' => array('file' => 'inc', 'module' => 'note_service'),
        'callback' => '_note_service_create',
        'access arguments' => array('note service create'),
        'access arguments append' => FALSE,
        'args' => array(
          array(
            'name' => 'data',
            'type' => 'struct',
            'description' => 'The note object',
            'source' => 'data',
            'optional' => FALSE,
          ),
        ),
      ),
      'update' => array(
        'help' => 'Updates a note',
        'file' => array('file' => 'inc', 'module' => 'note_service'),
        'callback' => '_note_service_update',
        'access callback' => '_note_service_access',
        'access arguments' => array('update'),
        'access arguments append' => TRUE,
        'args' => array(
          array(
            'name' => 'id',
            'type' => 'int',
            'description' => 'The id of the node to update',
            'source' => array('path' => '0'),
            'optional' => FALSE,
          ),
          array(
            'name' => 'data',
            'type' => 'struct',
            'description' => 'The note data object',
            'source' => 'data',
            'optional' => FALSE,
          ),
        ),
      ),
      'delete' => array(
        'help' => 'Deletes a note',
        'file' => array('file' => 'inc', 'module' => 'note_service'),
        'callback' => '_note_service_delete',
        'access callback' => '_note_service_access',
        'access arguments' => array('delete'),
        'access arguments append' => TRUE,
        'args' => array(
          array(
            'name' => 'nid',
            'type' => 'int',
            'description' => 'The id of the note to delete',
            'source' => array('path' => '0'),
            'optional' => FALSE,
          ),
        ),
      ),
      'index' => array(
        'help' => 'Retrieves a listing of notes',
        'file' => array('file' => 'inc', 'module' => 'note_service'),
        'callback' => '_note_service_index',
        'access callback' => 'user_access',
        'access arguments' => array('access content'),
        'access arguments append' => FALSE,
        'args' => array(
          array(
            'name' => 'page',
            'type' => 'int',
            'description' => '',
            'source' => array(
              'params' => 'page',
            ),
            'optional' => TRUE,
            'default value' => 0,
          ),
          array(
           'name' => 'parameters',
           'type' => 'array',
           'description' => '',
           'source' => 'param',
           'optional' => TRUE,
           'default value' => array(),
          ),
        ),
      ),
    ),
  );
}
?>

创建回调

要创建回调,您需要创建文件 note_service.inc,因为您会告诉 Services 在这里查找回调。要合并到 .inc 文件中的回调(CRUD 方法方面)包括:

  • 创建回调
  • 检索回调
  • 更新回调
  • 删除回调

您还应该包含一个索引回调,它抓取并返回特定用户的所有备注。在方法中的此处指定一些参数,即使不会使用它们。这些参数的用途是表明支持分页和过滤一个索引清单。

显然,不是所有用户都要有权限随意查看 Note 节点。这就是您应该包含访问回调的原因。这些访问回调实际上应该包含在主要模块文件。请注意,create 或 edit 函数都没有表示,因为这些函数直接使用 user_access( ) 语句。而且,与其他方法不同,该文件不会考虑备注所有权,它仅检查创建的 Note 服务的访问权限。清单 4 给出了 .inc 文件代码。

清单 4. Note service .inc 文件
<?php
// note_service.inc
/**
* Callback for creating note services.
*
* @param object $data
* @return object
*/
function _note_service_create($data) {
  global $user;

  unset($data->id);
  $data->uid = $user->uid;
  $data->created = time();
  $data->modified = time();

  if (!isset($data->subject)) {
    return services_error('Missing note attribute subject', 406);
  }

  if (!isset($data->note)) {
    return services_error('Missing note attribute note', 406);
  }

  note_service_write_note($data);
  
  return (object)array(
    'id' => $data->id,
    'uri' => services_service_uri(array('note', $data->id)),
  );
}

// note_service.inc
/**
* Callback for updating note services.
*
* @param int $id
* @param object $data
* @return object
*/
function _note_service_update($id, $data) {
  global $user;
  $note = note_service_get_note($id);

  unset($data->created);
  $data->id = $id;
  $data->uid = $note->uid;
  $data->modified = time();

  note_service_write_note($data);
  
  return (object)array(
    'id' => $id,
    'uri' => services_service_uri(array('note', $id)),
  );
} 
/**
* Callback for retrieving note services.
*
* @param int $id
* @return object
*/
function _note_service_retrieve($id) {
  return note_service_get_note($id);
}

/**
* Callback for deleting note services.
*
* @param int $id
* @return object
*/
function _note_service_delete($id) {
  note_service_delete_note($id);
  
  return (object)array(
    'id' => $id,
  );
}

function _note_service_index($page, $parameters) {
  global $user;

  $notes = array();
  $res = db_query("SELECT * FROM {note} 
WHERE uid='".$user->uid."'ORDER BY modified DESC");
  foreach ($res as $note) {
    $notes[] = $note;
  }

  return $notes;
}

/**
* Access callback for the note service.
*
* @param string $op
*  The operation that's going to be performed.
* @param array $args
*  The arguments that will be passed to the callback.
* @return bool
*  Whether access is given or not.
*/

function _note_service_access($op, $args) {
  global $user;
  $access = FALSE;

  switch ($op) {
    case 'view':
      $note = note_service_get_note($args[0]);
      $access = user_access('note service view any note');
      $access = $access || $note->uid == $user->uid && 
      user_access('note service view own notes');
      break;
    case 'update':
      $note = note_service_get_note($args[0]->id);
      $access = user_access('note service edit any note');
      $access = $access || $note->uid == $user->uid &&  
      user_access('note service edit own notes');
      break;
    case 'delete':
      $note = note_service_get_note($args[0]);
      $access = user_access('note service delete any note');
      $access = $access || $note->uid == $user->uid && 
      user_access('note service delete own notes');
      break;
  }
  $access = TRUE;
  
  return $access;
}
?>

当新的自定义模块文件夹中有了 .info 和 .module 文件之后,您可以在 Drupal 模块 Administration 页面上启用该模块。查找自定义 Note 服务模块,勾选该模块旁边的复选框以启用它,并保存模块配置。另外,启用 Services 和 REST 服务器模块。自定义 note_service 模块的创建到此就应该完成了。


创建端点

Web 服务表示一组可通过多种方式使用的软件工具,3 个最常见的 Web 服务是面向服务的架构 (SOA)、RPC 和 REST。RPC 是第一批 Web 服务工具的焦点,即使它与特定于语言的调用或方法调用的耦合太紧密。相反,REST 通过 GETPOSTPUTDELETE 等熟悉的操作使用 HTTP 和类似的协议,因此相比操作和消息,它与有状态资源的交互更多。两个服务器工具都包含在 Services 模块(参见 Servers 文件夹)中。您可以将其他工具下载到此文件夹中,但此示例主要关注 REST 方法,因为该端点是通用的。

端点 表示一个特定的位置,可使用特定的协议和数据格式在此处访问服务。可通过两种方式创建端点:通过代码或使用管理界面。通过管理界面创建端点是更简单的选项,因为您然后可以将代码导出并粘贴到您的模块中。要创建端点,执行以下步骤:

  1. Admin > Structure > Services 中,单击 Add
  2. 将端点命名为 note
  3. 选择 REST 作为您的服务器,将端点路径放在 note 上。
  4. 清除 Debug ModeSession Enabled 复选框。
  5. 单击 Save图 3 显示了正在创建的新服务端点。
    图 3. 新服务端点
    使用 Name=note、Server=REST、Path to endpoint=note 编辑端点备注的屏幕截图

    查看 图 3 的大图

  6. 单击 Services 链接,选择您希望启用的服务或服务组。清除 notes 复选框。
  7. 单击 Save
  8. 单击 Server 选项卡,然后清除 Request parsing 下的 application/x-www-form-urlencoded 复选框。
  9. 单击 Save

您现在已在您的 REST 服务器中创建了一个合适、有效的端点。


结束语

在本文中,我们学习了如何使用 Drupal Services 模块,安装并启用了辅助性的 Services 模块,了解它的服务器功能和服务功能。我们还了解到,可以通过添加服务器模块来与您的 Drupal 站点和内容相集成,使用预封装的 Services 子模块向外部网站提供 Drupal 内容。我们创建了一个可与您的 Services 模块集成并扩展其功能的自定义模块,创建了一个方法调用来返回一个回调,该回调应该输出具有特定内容类型的所有节点和内容的清单。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=807913
ArticleTitle=在 Drupal 7 中创建自定义 Web 服务项目
publish-date=04012012