“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
Drupal远不止用于网站开发,准确的描述Drupal是“一个适用于App开发、小程序、物联网、网站等的后端数据中心和控制中心”。在网站开发中,我们习惯于HTML,然而在APP开发等其他场景中,更多的是前后端分离,这就要用到RESTful模块了(这里不得不提一下Drupal数据架构的优越性,这是强大的RESTful模块的基础),此模块实现了一种web服务,它使用json、xml等数据格式在前后端进行通讯,这实现了前后端“强解耦”,由此后端系统变成了一个“中心”,前端可以有多种也可以有多个,和通常的网站系统相比整个事情就不一样了,这个Drupal中心可与任意系统通讯,向任意种类、任意数量的系统提供服务,这使得Drupal非常适合进行移动应用开发,比如一个Drupal后台同时驱动安卓应用、IOS应用、网站、小程序等;同时也非常适合现在流行的微服务架构设计。
在感叹Drupal强大能力的同时,对于开发者来说,RESTful模块也由此显得特别重要,本文仅讲解RESTful模块的使用,直观的列出许多示例,关于该模块的设计以及REST源的开发详见官网,实际上系统默认提供了强大的功能,我们很少需要自定义开发。
启用RESTful模块:
进入后台的扩展列表,Web services一节,要启用RESTful模块一般需要同时启用以下几个模块:
序列化模块(serialization):
这是RESTful模块的依赖项,必须启用,看名字似乎只是用于数据的序列化,好似几个函数的功能,实则远没那么简单,它不止提供实体等数据的序列化,还提供标准化,提供了REST的基础功能,默认提供了JSON和XML两种通讯格式,通过贡献模块,可以添加更多,该模块为格式添加提供了标准框架。
REST用户接口模块(restui)
该模块不是必须的,只是为了方便我们管理REST,有了它,我们可以方便的在后台启禁用和配置各种REST源,该模块和RESTful模块的关系,类似视图UI和视图模块的关系,这是一个贡献模块,推荐下载安装,如果没有安装该模块,我们需要手动修改REST源配置实体,这是很不方便的。
HTTP基本认证模块(basic_auth)
该模块不是必须的,在进行REST通讯时,需要进行权限控制,同样有“用户”这个概念,因此需要进行用户认证,请求Drupal时需要提供认证凭据,系统默认提供了cookie认证(这也是我们在HTML方式下默认的认证方式),该模块另外提供了基本认证“Basic Auth”,这种认证方式在HTTP头中提供用户名和密码信息,实际上传递的是用户名和密码的Base64编码值,即“base64_encode("用户名:密码");”,因此注意该方式相当于是以明文方式在每次请求中传递密码,不及cookie方式安全,但这两种方式都有可能在传输途中被第三方获取认证凭据,因此安全的做法是运行在https之上,如果我们不需要基本认证这种方式,也可以不启用该模块;如果需要其他认证方式可以下载各种贡献模块。
超文本应用语言模块(hal)
该模块不是必须的,但我们通常会使用它,HAL是一种基于json的数据格式,提供了API的自发现性,换句话说,返回的数据不只包含所请求数据本身,还包含上下页、链接关系、引用等有助于提供额外信息的元数据,这是一种标准,为跨系统通讯提供了通用规则,其规范详见:stateless.co/hal_specification.html
配置REST:
要使用REST需要先进行配置,模块启用后,默认开启了节点实体的访问,如果需要对其他类型数据启用REST访问需要启用并配置,地址为:/admin/config/services/rest
关于RESTful概念网上有许多资料,这里不多讲,简而言之就是采用相同的URL依托不同的HTTP方法对数据进行增删改查操作,增加数据时URL不带ID,删除、修改、查询时带ID。
启用并配置好后,下文讲述如何进行REST操作
权限控制:
REST操作完全采用(遵循)实体自有的访问控制,当注册好用户后,需要为其分配可用的权限,否则不会成功,为表明用户身份,客户端系统在发起REST通讯时需要携带用户认证凭据,见下例。
启用并配置好后,下文以示例讲述如何进行REST操作
示例一、PHP端发起创建:
本例以PHP语言作为客户端系统,发起REST创建一篇文章,代码如下:
$entity = [
'title' => [['value' => '云客测试']],
'body' => [['value' => '文章正文', "summary" => "摘要内容"]],
'type' => [['target_id' => 'article']],
'_links' => [
'type' => [
'href' => 'http://www.dp9.com/rest/type/node/article',
],
],
];
$httpOption = [
'auth' => ['yunke', '123456'], //提供基本用户认证用户名和密码
'json' => $entity,
'headers' => [
'Content-Type' => 'application/hal+json',
//重要必选,否则失败,指定请求BODY所提供数据的格式
'X-CSRF-Token' => 'sfsxy_dKXtc5WjbY4-RJEsn0LasWgm5dg8biiwvrBvc',
//跨域保护,仅在cookie认证下才需要,在本列中其实是不需要的,获取方法见后
],
];
$client = \Drupal::httpClient();
$url = 'http://www.dp9.com/node?_format=hal_json';
//地址可参考REST配置页,格式必选,用于指定响应数据的格式
$responseData = '';
try {
$response = $client->post($url, $httpOption); //注意一定是POST方法
$responseData = $response->getBody()->getContents();
} catch (RequestException $e) {
//HTTP状态码大于等于400 或者网络错误将报错触发这里
$msg = $e->getMessage() . "\n";
if ($e->hasResponse()) {
$msg .= "请求失败,响应状态码:" . $e->getResponse()->getStatusCode();
$msg .= "响应body:" . $e->getResponse()->getBody() . "\n";
}
$responseData = $msg;
}
header("Content-type: application/json");
print_r($responseData);
替换你的域名和认证信息,运行以上代码后,将创建一篇文章,可到后台查看
示例二、任意第三方系统端发起创建:
这里客户端系统以postman为例(一个HTTP调式工具,可到https://www.postman.com/下载),目标同上列一样去创建一篇文章。设置POST请求地址为(替换成你自己的域名):
http://www.dp9.com/node?_format=hal_json
在请求body中提交以下hal json格式数据:
{
"title":[
{"value":"yunke postman"}
],
"body":[
{"value":"正文内容","summary":"摘要内容"}
],
"type":[
{"target_id":"article"}
],
"_links":{
"type":{"href":"http://www.dp9.com/rest/type/node/article"}
}
}
请求头:
Content-Type: application/hal+json
(用于指示body的数据格式)
Authorization: Basic eXVua2U6MTIzNDU2
(采用基本认证,提供凭据,POSTMAN会自动计算,在认证选项卡设置为你自己的)
认证方式如果为cookie那么需要cookie头:
Cookie: SESS945389e78880c99dc7e5fd5ebc3045b9=3H34rFPmG7avBRHYV,gVsDBO7L92CQD59mil,JwSuHTaJB6U
(值为你自己的)
X-CSRF-Token: sfsxy_dKXtc5WjbY4-RJEsn0LasWgm5dg8biiwvrBvc
(为保护系统不受跨站请求伪造,在cookie认证方式下需要提供“X-CSRF-Token”头,其值可以到/session/token地址获取,或者在REST登录返回值中获取,见后,如果认证方式是基本认证,则不需要该请求头)
HTTP请求方法设置为POST,然后点击发送即可,系统会返回创建后的实体数据
示例三、通过REST上传图片、文件等:
这里以默认的文章内容类型为例,通过REST创建一篇带有图片的文章,客户端工具采用postman。一共分两步:先上传图片,然后上传文章:
第一步:
首先开启“File Upload”REST接口(可通过REST UI开启),这用于上传图片等文件,开启后POST请求的目标地址为:
http://www.dp9.com/file/upload/node/article/field_image/?_format=hal_json
即:/file/upload/{entity_type_id}/{bundle}/{field_name}
其他实体类型类似;设置以下请求头:
Content-Disposition: file; filename="yunke.png"
Content-Type: application/octet-stream
以及前列中权限凭据相关请求头,请求体直接发送文件内容,然后点击发送即可完成上传,上传成功后可到后台(/admin/content/files)查看,如你所见上传后的文件名由Content-Disposition头指定(所以该头是必须的),上传受到字段配置的限制,如储存位置、大小、允许的格式等等,如果不满足将不会成功。
第二步:
上传成功后会返回临时保存的文件实体数据,其中“fid.0. value”保存着文件实体ID,记住这个ID,它将用于后续创建文章实体。
接下来通过下面的方法创建带图片的文章:
{
"title":[
{"value":"带图片的文章"}
],
"body":[
{"value":"正文内容","summary":"摘要内容"}
],
"field_image": [
{"target_id": 1,"alt":"yunke图片"}
],
"type":[
{"target_id":"article"}
],
"_links":{
"type":{"href":"http://www.dp9.com/rest/type/node/article"}
}
}
在Drupal中所有的文件字段都是引用字段,这里关键步骤在于设置field_image字段的target_id值为前一步骤上传的文件的fid。
这样我们就上传了一篇带图片的文章了,其他文件或实体类型类似。
关于文件上传:
上传文件必须要验证,否则很危险(可能上传HTML、JS、php等),然而验证规则必须要来源于某处,在Drupal中只有实体类型的字段信息,因此上传文件仅有该方式(自定义REST源的文件上传除外),关于此的讨论请见:
https://www.drupal.org/project/drupal/issues/1927648
https://www.drupal.org/node/2941420
REST非创建操作:
更新和创建操作类似,但用HTTP的PATCH方法,为什么不用PUT方法的理由请参考:
https://groups.drupal.org/node/284948
更新时,BODY中可以只发送需要更改的数据字段。查看操作用GET方法,body无需数据,删除用DELETE方法,body无需数据。
和创建操作相比,非创建操作的请求URL中必须带有数据的ID。
用户登录与注册:
用户登录:
采用POST方法访问:http://www.dp9.com/user/login?_format=json
携带HTTP头:
Content-Type: application/json
无需其他头,请求body如下:
{
"name": "用户名",
"pass": "密码"
}
系统将返回类似如下响应:
{
"current_user": {
"uid": "8",
"name": "yunke"
},
"csrf_token": "T8LIrGmMVs2HVf2Vh_ekv8XBFKeMBClpGv8-u-bvZsA",
"logout_token": "AqvLzojLWxpgwOMVSjDXt-f3Xsn2tCgZ2Q75Xh3sZQw"
}
该响应body中csrf_token被用于前文例子中的X-CSRF-Token头的值,在后续请求中被需要(如果是cookie认证的话);logout_token用于退出登录,见下;这两个值每一次登录都不会相同;在响应头中同时包含了后续登录使用的会话cookie,有这些信息后面就可以执行其他登录后请求以及登出了
如果登录信息错误会返回类似如下内容:
{
"message": "Sorry, unrecognized username or password."
}
此时状态码为400,客户端可通过HTTP状态码来判断登录是否成功
思考:假设我们在html端为登录表单设置了验证码,通过以上方法登录还能成功吗?怎么提交验证码呢?其实那不会影响到REST登录,REST登录并不走登录表单的后台逻辑,也不需要验证码,没有验证码怎么防止机器人大量试探呢?不用担心,在后台有洪水控制,同一个IP当多次登录失败就会被锁定一段时间。
退出登录:
采用POST访问以下地址:
http://www.dp9.com/user/logout?_format=json&token=AqvLzojLWxpgwOMVSjDXt-f3Xsn2tCgZ2Q75Xh3sZQw
这里token的值来自登录时返回的logout_token值,见REST登录。
设置请求头Content-Type: application/json,以及相关认证头,然后发起访问
当成功退出时,会返回204响应,没有任何BODY输出
得到用户信息:
启用用户实体的REST源后,采用GET访问:http://www.dp9.com/user/8?_format=json
需要携带认证信息
用户注册:
启用用户注册REST源(User registration),并配置好权限允许匿名用户注册,然后POST访问以下地址:
http://www.dp9.com/user/register?_format=json
提交的BODY如下:
{
"name": { "value": "fooBar" },
"mail": { "value": "foo@bar.com" },
"pass": { "value": "secretSauce" }
}
注意:注册受到“管理-配置-人员-账户设置”的影响,如果要求邮件验证,那么以上请求会失败,会提示密码在验证后登陆时设置;当成功后会返回账户实体数据
补充:
一、REST模块的历史更改:
在Drupal8.2版本以前,所有的REST资源配置位于rest.settings.yml中,此后被转变储存到配置实体中,这样做的主要原因是需要具备配置依赖,在该版本上,还对权限控制进行了更改,不再需要REST专门的权限设置,而是统一采用实体访问控制API,参考:
https://www.drupal.org/node/2747231
https://www.drupal.org/node/2733435
二、参考文档:
官网模块文档地址:https://www.drupal.org/docs/8/core/modules/rest
三、在本文所有示例中,你可使用系统支持的任意格式,但需要在REST源配置中启用,并在“Content-Type”头、查询头参数等地方指定,注意不同格式数据内容略有不同,并不一一对应,比如json格式和hal_json格式相比,就没有“_links”字段。更多json示例见这里:
https://www.drupal.org/docs/8/core/modules/rest/javascript-and-drupal-8-restful-web-services
四、关于前后端分离,Drupal还提供了jsonAPI,它和REST的区别请参见本系列JSON api主题
反馈互动