使用 Dingo API 快速构建 RESTful API(十一)—— 在应用内部请求 Dingo API


Dingo 扩展包花费了很大的精力来提供从应用内部请求 Dingo API 接口的功能,这意味着,通过 Dingo 扩展包实现的 API 接口不仅可以被客户端和第三方应用消费,还可以为应用内部的 Web 路由和其他 API 路由提供数据和服务支持,这样一来,我们就可以构建一个完全基于 API 驱动的应用,支持内部请求的 API 还有一个好处就是可以返回原生数据对象而不是 HTTP 响应对象,因此,我们可以基于 Laravel 及 Dingo 提供的语法糖来处理返回的结果或者处理抛出的异常。

下面我们就来看一下如何在 Laravel 应用中通过内部请求消费 Dingo API,要使用内部请求功能,先要获取一个调度器实例:

$dispatcher = app(\Dingo\Api\Dispatcher::class);

快速入门

在 API 路由中请求 API

首先,我们可以在 Dingo API 中消费其它的 API 接口:

$api->version('v3', function ($api) {
    ...
    $api->get('task/{id}', function ($id) {
        $dispatcher = app(\Dingo\Api\Dispatcher::class);
        $task = $dispatcher->version('v1')->get('dingoapi/task/' . $id);
        dd($task);
    });
});

在这个示例中,我们在 Dingo API 的 v3 版本分组中定义一个基于 v1 版本 task/{id} 接口返回数据的路由,在获取到调度器实例后,通过 version 方法指定要消费的 Dingo API 版本,然后通过 get 方法对相应的路由发起 GET 请求返回数据,这样我们就可以获取到对应的任务数据:

Dingo 内部请求

在 Web 路由中请求 API

然后,我们可以在 routes/web.php 中定义一个消费上述 Dingo API 的 Web 路由:

$dispatcher = app(\Dingo\Api\Dispatcher::class);
Route::get('/task/{id}', function ($id) use ($dispatcher) {
    $task = $dispatcher->version('v3')->get('dingoapi/task/' . $id);
    dd($task);
});

这次,我们消费的是 v3 版本的 Dingo API 接口,返回数据和上面一样。

内部请求认证 API 接口

在已认证 API 路由中请求

在已认证的 API 路由中请求 Dingo API 的认证接口比较简单,不需要做任何额外的操作,因为已经认证的 API 接口中发起内部请求中会自动带上 Authorization 字段。

在未认证或 Web 路由中请求

在未认证 API 路由或 Web 路由中对 Dingo API 认证接口发起内部请求需要将访问令牌设置到请求头 Authorization 字段中才可以,我们以之前待办任务列表中的 home 路由为例,这是一个 Web 路由,虽然需要认证才能访问,但是 Web 路由认证和 API 路由认证走的是两套体系,之间并不相通,内部请求 的 API 接口需要单独认证,这里我们借助 Passport 私人访问令牌来实现该 API 接口的认证,打开 app/Http/Controllers/HomeController.php,将 index 方法中获取任务列表的逻辑改为基于 Dingo API v3 版本的 tasks.index 接口获取,该接口是需要认证的,我们通过 Passport 私人访问令牌生成访问令牌,然后将其设置到 Authorization 请求头:

public function index(Request $request, Dispatcher $dispatcher)
{
    $params = [];
    if ($request->has('page')) {
        $params['page'] = $request->get('page');
    }
    if ($request->has('limit')) {
        $params['limit'] = $request->get('limit');
    }
    $token = $request->user()->createToken('Internal Request Token')->accessToken;
    $tasks = $dispatcher->on('todo.test')->header('Authorization', 'Bearer ' . $token)->version('v3')->get('dingoapi/tasks', $params);
    return view('home', ['tasks' => json_encode($tasks->items())]);
}

由于 tasks.index API 接口还支持分页数据,我们将相应的参数通过调度器实例的 get 方法的第二个参数传递过去。

接口返回的并不是 HTTP 响应实例,而是包含任务列表和分页信息的分页器实例,通过调用该实例上的 items 方法即可返回任务列表数据。

当然为了让 Passport 私人访问令牌生效,还需要通过如下命令创建对应的数据表记录以便用于生成令牌信息:

php artisan passport:client --personal

如果之前已经运行过相关命令或者 oauth_clients 表中已经存在 Laravel Personal Access Client 记录,也可以直接在 AuthServiceProviderboot 方法中通过 Passport 的personalAccessClientId 方法直接使用该记录:

public function boot()
{
    ...
    Passport::personalAccessClientId('client-id');
}

client_id 替换成相应的数据库主键 ID 即可。

访问 http://todo.test/home 页面,登录成功之后就可以看到任务列表信息了,和之前返回结果一样:

基于Dingo API内部请求获取任务列表

不同的是现在还支持分页功能,这可以通过在请求参数中设置页码和每页显示数量来实现:

基于Dingo API内部请求获取任务分页数据

更多特性

以上就已经介绍完最常见的通过 GET 方式对 Dingo API 发起的内部请求,此外,Dingo 调度器还支持很多其他方法,通过这些方法,我们可以实现更多更复杂的内部请求,下面我们就一些常见的场景进行介绍。

发起 POST 请求

除了 GET 请求外,Dispatcher 调度器还支持 POST、PUT、PATCH、DELETE 等其它常见的 HTTP 请求方法,以 POST 请求为例,对应的方法名是 post,我们可以这样通过 POST 请求发送数据:

$dispatcher->with(['name' => '学院君', 'location' => '杭州'])->post('users');

还可以这样:

$dispatcher->post('users', ['name' => '学院君', 'location' => '杭州']);

PUT、PATCH、DELETE 对应的内部请求发送方式也与 POST 一样,这里就不再赘述,详情请看 vendor/dingo/api/src/Dispatcher.php 源码。

上传文件

Dingo API 调度器提供了多种方式来支持上传文件请求,你可以通过传入 Symfony\Component\HttpFoundation\File\UploadedFile 实例到 attach 方法来添加文件到 POST 请求:

$dispatcher->attach(Input::files())->post('photos');

也可以通过数组方式传入文件标识与路径信息来上传文件:

$dispatcher->attach(['photo' => 'photos/me.jpg'])->post('photos');

这种方式还支持一次上传多个文件以及设置多个文件元信息(这种方式避免了服务端计算文件类型和尺寸):

$dispatcher->attach([
    'photo' => [
        'path' => 'photos/me.jpg',
        'mime' => 'image/jpeg',
        'size' => '49430'
    ]
])->post('photos');

发送 JSON 格式请求数据

还可以通过调度器上的 json 方法发送 JSON 格式请求数据:

$data = ['name' => 'xueyuanjun', 'password' => '12345678'];
$dispatcher->json($data)->post('users');

同时请求头中 Content-Type 字段会被设置为 application/json

获取原生响应对象

内部请求默认返回的是未经转化和格式化的原生数据对象,如果你想要获取响应对象,可以调用 raw 方法实现:

$response = $dispatcher->raw()->get('users');

异常及错误处理

通过内部请求调用 Dingo API 时,所有接口处理过程中抛出的异常都需要手动捕获并处理,比如 Api\TaskControllerstore 方法抛出冲突异常:

use Symfony\Component\HttpKernel\Exception\ConflictHttpException;

public function store(CreateTaskRequest $request)
{
    throw new ConflictHttpException('we got a conflict!');
}

如果对该方法对应 API 路由发起内部请求,则需要手动捕获抛出的异常:

try {
    app(\Dingo\Api\Dispatcher::class)->with($payload)->post('dingoapi/tasks');
} catch (Symfony\Component\HttpKernel\Exception\ConflictHttpException $e) {
    // Do something here, like return with an error.
}

如果某个 API 接口返回的是错误响应,比如 show 方法这样:

public function show($id)
{
    if (!is_numeric($id)) {
        return $this->response->errorBadRequest();
    }
    $task = Task::find($id);
    if (!$task) {
        return $this->response->errorNotFound();
    }
    ...
}

则在对其发起内部请求的时候,需要捕获 Dingo\Api\Exception\InternalHttpException 异常:

try {
    $task = $dispatcher->version('v3')->get('dingoapi/tasks/' . $id);
} catch (\Dingo\Api\Exception\InternalHttpException $exception) {
    return $exception->getResponse();
}

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 使用 Dingo API 快速构建 RESTful API(十)—— 路由访问频率限制

>> 下一篇: 使用 Dingo API 快速构建 RESTful API(十二)—— 生成 API 文档