vanilla / garden-http
一个精简的HTTP客户端库,用于构建RESTful API客户端。
Requires
- php: >=7.4
- ext-curl: *
- ext-json: *
- psr/http-message: >=1.0
- slim/psr7: ^1.6
- vanilla/garden-utils: ^1.1
Requires (Dev)
- phpunit/phpunit: ^9.0
- slim/http: ^1.3
- slim/slim: ^4.11
- dev-master
- v2.9.0
- v2.8.4
- v2.8.3
- v2.8.2
- v2.8.1
- v2.8
- v2.7
- v2.6
- v2.5.2
- v2.5.1
- v2.5
- v2.4
- v2.3
- v2.2
- v2.1.1
- v2.1
- v2.0.2
- v2.0.1
- v2.0.0
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- dev-feature/connect-timeout-and-exceptions
- dev-feature/more-mocks
- dev-feature/psr7
- dev-feature/better-exceptions
- dev-fix/php-fatal-error-vnla-3512
- dev-feature/github-actions
- dev-fix/MockRequest-match-function-optional-path-check
- dev-add/optional-bodyrequest-on-mockhttpresponsetrait-vnla-1649
- dev-feature/add-keep-alive
- dev-feature/client-handler
- dev-feature/decouple-client
- dev-fix/user-agent
- dev-fix/build
- dev-release/1.1
This package is auto-updated.
Last update: 2024-09-10 22:03:03 UTC
README
Garden HTTP是一个精简的HTTP客户端库,用于构建RESTful API客户端。它旨在允许您在不复制/粘贴大量cURL设置的情况下,也不需要将您的代码库大小翻倍的情况下访问人们的API。您可以直接使用此库作为快速API客户端,也可以扩展HttpClient
类以创建您经常使用的结构化API客户端。
安装
Garden HTTP需要PHP 7.4或更高版本以及libcurl
Garden HTTP遵守PSR-4,并可以使用composer安装。只需将vanilla/garden-http
添加到您的composer.json中。
Garden请求和响应对象也遵守PSR-7。
基本示例
几乎所有使用Garden HTTP的情况都涉及首先创建一个HttpClient
对象,然后从中发出请求。您可以看到,以下示例还设置了一个默认头,以便将标准头传递给使用客户端发出的每个请求。
use Garden\Http\HttpClient; $api = new HttpClient('http://httpbin.org'); $api->setDefaultHeader('Content-Type', 'application/json'); // Get some data from the API. $response = $api->get('/get'); // requests off of base url if ($response->isSuccessful()) { $data = $response->getBody(); // returns array of json decoded data } $response = $api->post('https://httpbin.org/post', ['foo' => 'bar']); if ($response->isResponseClass('2xx')) { // Access the response like an array. $posted = $response['json']; // should be ['foo' => 'bar'] }
抛出异常
您可以让HTTP客户端在请求失败时抛出异常。
use Garden\Http\HttpClient; $api = new HttpClient('https://httpbin.org'); $api->setThrowExceptions(true); try { $api->get('/status/404'); } catch (\Exception $ex) { $code = $ex->getCode(); // should be 404 throw $ex; } // If you don't want a specific request to throw. $response = $api->get("/status/500", [], [], ["throw" => false]); // But you could throw it yourself. if (!$response->isSuccessful()) { throw $response->asException(); }
将抛出包含失败响应和结构化数据的消息的异常。
try { $response = new HttpResponse(501, ["content-type" => "application/json"], '{"message":"Some error occured."}'); throw $response->asException(); // Make an exception } catch (\Garden\Http\HttpResponseException $ex) { // Request POST /some/path failed with a response code of 501 and a custom message of "Some error occured." $ex->getMessage(); // [ // "request" => [ // 'url' => '/some/path', // 'method' => 'POST', // ], // "response" => [ // 'statusCode' => 501, // 'content-type' => 'application/json', // 'body' => '{"message":"Some error occured."}', // ] // ] $ex->getContext(); // It's serializable too. json_encode($ex); }
基本认证
您可以使用auth
选项指定基本认证的用户名和密码。
use Garden\Http\HttpClient; $api = new HttpClient('https://httpbin.org'); $api->setDefaultOption('auth', ['username', 'password123']); // This request is made with the default authentication set above. $r1 = $api->get('/basic-auth/username/password123'); // This request overrides the basic authentication. $r2 = $api->get('/basic-auth/username/password', [], [], ['auth' => ['username', 'password']]);
通过子类化扩展HttpClient
如果您将反复调用相同的API,您可能想通过扩展HttpClient
类来创建一个更易于重用的API客户端。
use Garden\Http\HttpClient; use Garden\Http\HttpHandlerInterface // A custom HTTP client to access the github API. class GithubClient extends HttpClient { // Set default options in your constructor. public function __construct(HttpHandlerInterface $handler = null) { parent::__construct('https://api.github.com', $handler); $this ->setDefaultHeader('Content-Type', 'application/json') ->setThrowExceptions(true); } // Use a default header to authorize every request. public function setAccessToken($token) { $this->setDefaultHeader('Authorization', "Bearer $token"); } // Get the repos for a given user. public function getRepos($username = '') { if ($username) { return $this->get("/users/$username/repos"); } else { return $this->get("/user/repos"); // my repos } } // Create a new repo. public function createRepo($name, $description, $private) { return $this->post( '/user/repos', ['name' => $name, 'description' => $description, 'private' => $private] ); } // Get a repo. public function getRepo($owner, $repo) { return $this->get("/repos/$owner/$repo"); } // Edit a repo. public function editRepo($owner, $repo, $name, $description = null, $private = null) { return $this->patch( "/repos/$owner/$repo", ['name' => $name, 'description' => $description, 'private' => $private] ); } // Different APIs will return different responses on errors. // Override this method to handle errors in a way that is appropriate for the API. public function handleErrorResponse(HttpResponse $response, $options = []) { if ($this->val('throw', $options, $this->throwExceptions)) { $body = $response->getBody(); if (is_array($body)) { $message = $this->val('message', $body, $response->getReasonPhrase()); } else { $message = $response->getReasonPhrase(); } throw new \HttpResponseExceptionException($response, $message); } } }
通过中间件扩展HttpClient
HttpClient
类有一个addMiddleware()
方法,允许您添加一个可以在发送前后修改请求和响应的功能。中间件允许您开发一组可重用的实用工具库,可以与任何客户端一起使用。中间件适用于诸如高级认证、缓存层、CORS支持等。
编写中间件
中间件是一个接受两个参数的可调用对象:一个HttpRequest
对象,以及下一个中间件。每个中间件都必须返回一个HttpResponse
对象。
function (HttpRequest $request, callable $next): HttpResponse { // Do something to the request. $request->setHeader('X-Foo', '...'); // Call the next middleware to get the response. $response = $next($request); // Do something to the response. $response->setHeader('Cache-Control', 'public, max-age=31536000'); return $response; }
您必须调用$next
,否则请求将不会被HttpClient
处理。当然,您可能希望在缓存层等情况下中断请求的处理,在这种情况下,您可以省略对$next
的调用。
示例:使用中间件修改请求
考虑以下实现HMAC SHA256哈希的类,该类为需要不仅仅是静态访问令牌的假设API。
class HmacMiddleware { protected $apiKey; protected $secret; public function __construct(string $apiKey, string $secret) { $this->apiKey = $apiKey; $this->secret = $secret; } public function __invoke(HttpRequest $request, callable $next): HttpResponse { $msg = time().$this->apiKey; $sig = hash_hmac('sha256', $msg, $this->secret); $request->setHeader('Authorization', "$msg.$sig"); return $next($request); } }
此中间件为每个请求计算一个新的授权头并将其添加到请求中。然后它调用$next
闭包以执行请求的其余部分。
HttpHandlerInterface
在Garden HTTP中,请求是通过HTTP处理器执行的。当前包含的默认处理器使用cURL执行请求。但是,您可以按照自己的方式实现HttpHandlerInterface
,并完全改变处理请求的方式。该接口只包含一个方法
public function send(HttpRequest $request): HttpResponse;
该方法旨在将请求转换为响应。要使用它,只需将HttpRequest
对象传递给它即可。
您还可以使用自定义处理器与HttpClient
一起使用。只需将其传递给构造函数即可。
$api = new HttpClient('https://example.com', new CustomHandler());
检查请求和响应
有时当你收到一个响应时,你想要知道是什么请求生成的。HttpResponse
类有一个getRequest()
方法用于此。HttpRequest
类有一个getResponse()
方法用于反向操作。
从HttpClient
对象抛出的异常是HttpResponseException
类的实例。该类有getRequest()
和getResponse()
方法,这样你可以检查异常的请求和响应。这种异常特别有用,因为请求对象是在客户端内部创建的,而不是由程序员直接创建的。
用于测试的模拟
提供了一种HttpHandlerInterface
实现和工具,用于模拟请求和响应。
设置
use Garden\Http\HttpClient use Garden\Http\Mocks\MockHttpHandler; // Manually apply the handler. $httpClient = new HttpClient(); $mockHandler = new MockHttpHandler(); $httpClient->setHandler($mockHandler); // Automatically apply a handler to `HttpClient` instances. // You can call this again later to retrieve the same handler. $mockHandler = MockHttpHandler::mock(); // Don't forget this in your phpunit `teardown()` MockHttpHandler::clearMock();; // Reset the handler instance $mockHandler->reset();
模拟请求
use Garden\Http\Mocks\MockHttpHandler; use Garden\Http\Mocks\MockResponse; // By default this will return 404 for all requests. $mockHttp = MockHttpHandler::mock(); $mockHttp // Explicit request and response ->addMockRequest( new \Garden\Http\HttpRequest("GET", "https://domain.com/some/url"), new \Garden\Http\HttpResponse(200, ["content-type" => "application/json"], '{"json": "here"}'), ) // Shorthand ->addMockRequest( "GET https://domain.com/some/url", MockResponse::json(["json" => "here"]) ) // Even shorter-hand // Mocking 200 JSON responses to GET requests is very easy. ->addMockRequest( "https://domain.com/some/url", ["json" => "here"] ) // Wildcards // Wildcards match with lower priority than explicitly matching requests. // Explicit wildcard hostname. ->addMockRequest("https://*/some/path", MockResponse::success()) // Implied wildcard hostname. ->addMockRequest("/some/path", MockResponse::success()) // wildcard in path ->addMockRequest("https://some-doain.com/some/*", MockResponse::success()) // Total wildcard ->addMockRequest("*", MockResponse::notFound()) ; // Mock multiple requests at once $mockHttp->mockMulti([ "GET /some/path" => MockResponse::success() "POST /other/path" => MockResponse::json([]) ]);
响应序列
在任何可以使用模拟HttpResponse
的地方,您也可以使用MockHttpSequence
。
推入序列的每个项将只返回一次。一旦响应被返回,它将不会再次返回。
如果整个序列耗尽,它将返回404响应。
use Garden\Http\Mocks\MockHttpHandler; use Garden\Http\Mocks\MockResponse; $mockHttp = MockHttpHandler::mock(); $mockHttp->mockMulti([ "GET /some/path" => MockResponse::sequence() ->push(new \Garden\Http\HttpResponse(500, [], "")) ->push(MockResponse::success()) ->push(MockResponse::json([]) ->push([]) // Implied json , ]);
响应函数
您可以通过提供一个可调用对象使模拟动态化。
use Garden\Http\Mocks\MockHttpHandler; use Garden\Http\Mocks\MockResponse; use \Garden\Http\HttpRequest; use \Garden\Http\HttpResponse; $mockHttp = MockHttpHandler::mock(); $mockHttp->addMockRequest("*", function (\Garden\Http\HttpRequest $request): HttpResponse { return MockResponse::json([ "requestedUrl" => $request->getUrl(), ]); })
对请求的断言
提供了一些工具,可以对已执行的请求进行断言。这对于使用通配符响应特别有用。
use Garden\Http\Mocks\MockHttpHandler; use Garden\Http\Mocks\MockResponse; use Garden\Http\HttpRequest; $mockHttp = MockHttpHandler::mock(); $mockHttp->addMockRequest("*", MockResponse::success()); // Ensure no requests were made. $mockHttp->assertNothingSent(); // Check that a request was made $foundRequest = $mockHttp->assertSent(fn (HttpRequest $request) => $request->getUri()->getPath() === "/some/path"); // Check that a request was not made. $foundRequest = $mockHttp->assertNotSent(fn (HttpRequest $request) => $request->getUri()->getPath() === "/some/path"); // Clear the history (and mocked requests) $mockHttp->reset();