xp-forge/rest-client

REST 客户端

v5.6.0 2024-03-24 13:23 UTC

README

Build status on GitHub XP Framework Module BSD Licence Requires PHP 7.0+ Supports PHP 8.0+ Latest Stable Version

REST 客户端

用法

Endpoint 类作为此 API 的入口点。使用 REST 服务的端点 URL 创建它的一个新实例,然后调用其 resource() 方法来处理资源。

创建:post

use webservices\rest\Endpoint;

$api= new Endpoint('https://api.example.com/');
$result= $api->resource('users')->post(['name' => 'Test'], 'application/json');

// Get location from created response, raising an UnexpectedStatus
// exception for any statuscode outside of the range 200-299.
$url= $result->location();

// Same as above, but handle 201 *AND* 200 status codes - see
// https://stackoverflow.com/questions/1860645
$id= $result->match([
  200 => fn($r) => $r->value()['id'],
  201 => fn($r) => (int)basename($r->location())
]);

读取:get / head

use webservices\rest\Endpoint;

$api= new Endpoint('https://api.example.com/');

// Test for existance with HEAD, raising UnexpectedStatus exceptions
// for any status code other than 200 and 404.
$exists= $api->resource('users/1549')->head()->match([
  200 => true,
  404 => false
]);

// Return user object, raising an UnexpectedStatus exception for any
// statuscode outside of the range 200-299.
$user= $api->resource('users/self')->get()->value();

// Same as above, but returns NULL for 404s instead of an exception
$user= $api->resource('users/{0}', [$id])->get()->optional();

// Pass parameters
$list= $api->resource('user')->get(['page' => 1, 'per_page' => 50])->value();

// Access pagination via `Link: <...>; rel="next"` header
$resource= 'groups';
do {
  $result= $this->endpoint->resource($resource)->get(['per_page' => 200]);
  foreach ($result->value() as $group) {
    yield $group['id'] => $group;
  }
} while ($resource= $result->link('next'));

更新:put / patch

use webservices\rest\Endpoint;

$api= new Endpoint('https://api.example.com/');
$resource= $api->resource('users/self')
  ->sending('application/json')
  ->accepting('application/json')
;

// Default content type and accept types set on resource used
$updated= $resource->put(['name' => 'Tested', 'login' => $mail])->value();

// Resources can be reused!
$updated= $resource->patch(['name' => 'Changed'])->value();

删除:delete

use webservices\rest\Endpoint;

$api= new Endpoint('https://api.example.com/');

// Pass segments, map 204 to true, 404 to null, raise UnexpectedStatus
// exception otherwise
$api->resource('users/{id}', $user)->delete()->match([
  204 => true,
  404 => null
]);

上传

多部分文件上传通过 upload() 方法启动,可以包含参数,并可以从任何输入流上传。

use io\File;
use io\streams\MemoryInputStream;
use webservices\rest\Endpoint;

$stream= new MemoryInputStream('Hello');
$file= new File(...);
$endpoint= new Endpoint($url);

$result= $endpoint->resource('files')->upload()
  ->pass('tc', 'accepted')
  ->transfer('letter', $stream, 'letter.txt', 'text/plain')
  ->transfer('cv', $file->in(), $file->filename)
  ->finish()
  
;

反序列化

通过传递类型到 value() 方法,支持通过自动结果反序列化。

use com\example\api\types\User;

$result= $api->resource('users/{0}', [$id])->get();

// If a type is passed, the result will be unmarshalled to an object
$map= $result->value();
$object= $result->value(User::class);

// Same for optional, but map and object will be NULL for 404s
$map= $result->optional();
$object= $result->optional(User::class);

// Works with any type from the XP typesystem, e.g. arrays of objects
$list= $api->resource('users')->get()->value('org.example.User[]');

错误处理

Result 类的操作会引发 UnexpectedStatus 异常。以下是如何访问它们的状态和原因

use webservices\rest\UnexpectedStatus;
use util\cmd\Console;

// In unexpected cases
try {
  $user= $api->resource('users/self')->get()->value();
} catch (UnexpectedStatus $e) {
  Console::writeLine('Unexpected ', $e->status(), ': ', $e->reason());
}

// More graceful handling
$result= $api->resource('users/self')->get();
if ($error= $result->error()) {
  Console::writeLine('Unexpected ', $result->status(), ': ', $error);
} else {
  $user= $result->value();
}

身份验证

支持基本身份验证,通过在端点 URL 中嵌入凭据来实现。

use webservices\rest\Endpoint;

$api= new Endpoint('https://user:pass@api.example.com/');

也可以在端点 URL 中嵌入 Bearer 令牌。

use webservices\rest\Endpoint;

$api= new Endpoint('https://token@api.example.com/');

可以按照以下方式传递基于头的其他身份验证值

use webservices\rest\Endpoint;

$api= (new Endpoint('https://api.example.com/'))->with(['X-API-Key' => $key]);

压缩

此库透明地处理压缩数据,发送一个包含 PHP 设置中支持的压缩算法的 Accept-Encoding 头(基于加载的扩展,如例如 zlib),并使用 Content-Encoding 响应头来确定选择哪个算法。

use webservices\rest\Endpoint;
use io\streams\Compression;

// Detect supported compression algorithms and set "Accept-Encoding" accordingly
$endpoint= new Endpoint($api);

// Send "Accept-Encoding: identity", indicating the server should not compress
$endpoint= (new Endpoint($api))->compressing(Compression::$NONE);

// Send "Accept-Encoding: gzip, br"
$endpoint= (new Endpoint($api))->compressing(['gzip', 'br']);

// Do not send an "Accept-Encoding" header, i.e. no preference is expressed
$endpoint= (new Endpoint($api))->compressing(null);

可测试性

此库还包括方便编写对 REST API 进行调用的代码的单元测试的功能。通过使用 TestEndpoint 类并向其提供它应该响应的路由,可以轻松测试各种场景,而无需 HTTP 协议和 I/O 开销。

use webservices\rest\TestEndpoint;

$endpoint= new TestEndpoint([
  '/users/6100' => function($call) {
    return $call->respond(200, 'OK', ['Content-Type' => 'application/json'], '{
      "id": 6100,
      "username": "binford"
    }');
  },
  'POST /users' => function($call) {
    return $call->respond(201, 'Created', ['Location' => '/users/6100']);
  },
]);

$response= $endpoint->resource('/users/me')->get();
// Responds with HTTP status 200 and the above JSON payload