156. Drupal移动APP、物联网开发之RESTful使用篇

Drupal远不止用于网站开发,准确的描述Drupal是“一个适用于App开发、小程序、物联网、网站等的后端数据中心和控制中心”。在网站开发中,我们习惯于HTML,然而在APP开发等其他场景中,更多的是前后端分离,这就要用到RESTful模块了(这里不得不提一下Drupal数据架构的优越性,这是强大的RESTful模块的基础),此模块实现了一种web服务,它使用jsonxml等数据格式在前后端进行通讯,这实现了前后端“强解耦”,由此后端系统变成了一个“中心”,前端可以有多种也可以有多个,和通常的网站系统相比整个事情就不一样了,这个Drupal中心可与任意系统通讯,向任意种类、任意数量的系统提供服务,这使得Drupal非常适合进行移动应用开发,比如一个Drupal后台同时驱动安卓应用、IOS应用、网站、小程序等;同时也非常适合现在流行的微服务架构设计。

在感叹Drupal强大能力的同时,对于开发者来说,RESTful模块也由此显得特别重要,本文仅讲解RESTful模块的使用,直观的列出许多示例,关于该模块的设计以及REST源的开发将在开发篇中讲解,实际上系统默认提供了强大的功能,我们很少需要自定义开发。

 

启用RESTful模块:

进入后台的扩展列表,Web services一节,要启用RESTful模块一般需要同时启用以下几个模块:

序列化模块(serialization):

这是RESTful模块的依赖项,必须启用,看名字似乎只是用于数据的序列化,好似几个函数的功能,实则远没那么简单,它不止提供实体等数据的序列化,还提供标准化,提供了REST的基础功能,默认提供了JSONXML两种通讯格式,通过贡献模块,可以添加更多,该模块为格式添加提供了标准框架。

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 UploadREST接口(可通过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

这样我们就上传了一篇带图片的文章了,其他文件或实体类型类似。

 

关于文件上传:

上传文件必须要验证,否则很危险(可能上传HTMLJSphp等),然而验证规则必须要来源于某处,在Drupal中只有实体类型的字段信息,因此上传文件仅有该方式(自定义REST源的文件上传除外),关于此的讨论请见:

https://www.drupal.org/project/drupal/issues/1927648

https://www.drupal.org/node/2941420

 

REST非创建操作:

更新和创建操作类似,但用HTTPPATCH方法,为什么不用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"
}

该响应bodycsrf_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主题

添加新评论

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。
请输入以上问题的答案,换一个问题请刷新,不区分大小写