mirror of
https://github.com/awslabs/aws-crt-php.git
synced 2026-08-18 01:57:13 +00:00
HTTP message API (#19)
* HTTP message API * Added MacOS CI * Grabbed fixes for mac from aws-crt-ffi
This commit is contained in:
@@ -203,4 +203,12 @@ final class CRT {
|
||||
function input_stream_get_length($stream) {
|
||||
return self::$impl->aws_crt_input_stream_get_length($stream);
|
||||
}
|
||||
|
||||
function http_message_new_from_blob($blob) {
|
||||
return self::$impl->aws_crt_http_message_new_from_blob($blob);
|
||||
}
|
||||
|
||||
function http_message_release($message) {
|
||||
self::$impl->aws_crt_http_message_release($message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace AWS\CRT\HTTP;
|
||||
|
||||
use AWS\CRT\Internal\Encoding;
|
||||
|
||||
final class Headers {
|
||||
private $headers;
|
||||
|
||||
public function __construct($headers = []) {
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public static function marshall($headers) {
|
||||
$buf = "";
|
||||
foreach ($headers->headers as $header => $value) {
|
||||
$buf .= Encoding::encodeString($header);
|
||||
$buf .= Encoding::encodeString($value);
|
||||
}
|
||||
return $buf;
|
||||
}
|
||||
|
||||
public static function unmarshall($buf) {
|
||||
$strings = Encoding::readStrings($buf);
|
||||
$headers = [];
|
||||
for ($idx = 0; $idx < count($strings);) {
|
||||
$headers[$strings[$idx++]] = $strings[$idx++];
|
||||
}
|
||||
return new Headers($headers);
|
||||
}
|
||||
|
||||
public function count() {
|
||||
return count($this->headers);
|
||||
}
|
||||
|
||||
public function get($header) {
|
||||
return isset($this->headers[$header]) ? $this->headers[$header] : null;
|
||||
}
|
||||
|
||||
public function set($header, $value) {
|
||||
$this->headers[$header] = $value;
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return array_merge($this->headers);
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,87 @@
|
||||
|
||||
namespace AWS\CRT\HTTP;
|
||||
|
||||
abstract class Message {
|
||||
private $path = "";
|
||||
private $query = [];
|
||||
private $headers = [];
|
||||
use AWS\CRT\NativeResource;
|
||||
|
||||
public function __construct($path, $query = [], $headers = []) {
|
||||
use AWS\CRT\Internal\Encoding;
|
||||
|
||||
abstract class Message extends NativeResource {
|
||||
private $method;
|
||||
private $path;
|
||||
private $query;
|
||||
private $headers;
|
||||
|
||||
public function __construct($method, $path, $query = [], $headers = null) {
|
||||
$this->method = $method;
|
||||
$this->path = $path;
|
||||
$this->query = $query;
|
||||
$this->headers = $headers;
|
||||
$this->headers = !is_null($headers) ? $headers : new Headers();
|
||||
$this->acquire(self::$crt->http_message_new_from_blob(self::marshall($this)));
|
||||
}
|
||||
}
|
||||
|
||||
class Request extends Message {
|
||||
private $body_stream = null;
|
||||
public function __destruct() {
|
||||
self::$crt->http_message_release($this->release());
|
||||
parent::__destruct();
|
||||
}
|
||||
|
||||
protected static function marshall($msg) {
|
||||
$buf = "";
|
||||
$buf .= Encoding::encodeString($msg->method);
|
||||
$buf .= Encoding::encodeString($msg->pathAndQuery());
|
||||
$buf .= Headers::marshall($msg->headers);
|
||||
return $buf;
|
||||
}
|
||||
|
||||
protected static function _unmarshall($buf, $class=Message::class) {
|
||||
$method = Encoding::readString($buf);
|
||||
$path_and_query = Encoding::readString($buf);
|
||||
$parts = explode("?", $path_and_query, 2);
|
||||
$path = isset($parts[0]) ? $parts[0] : "";
|
||||
$query = isset($parts[1]) ? $parts[1] : "";
|
||||
$headers = Headers::unmarshall($buf);
|
||||
|
||||
// Turn query params back into a dictionary
|
||||
if (strlen($query)) {
|
||||
$query = rawurldecode($query);
|
||||
$query = explode("&", $query);
|
||||
$query = array_reduce($query, function($params, $pair) {
|
||||
list($param, $value) = explode("=", $pair, 2);
|
||||
$params[$param] = $value;
|
||||
return $params;
|
||||
}, []);
|
||||
} else {
|
||||
$query = [];
|
||||
}
|
||||
|
||||
return new $class($method, $path, $query, $headers);
|
||||
}
|
||||
|
||||
public function pathAndQuery() {
|
||||
$path = $this->path;
|
||||
$queries = [];
|
||||
foreach ($this->query as $param => $value) {
|
||||
$queries []= urlencode($param) . "=" . urlencode($value);
|
||||
}
|
||||
$query = implode("&", $queries);
|
||||
if (strlen($query)) {
|
||||
$path = implode("?", [$path, $query]);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function __construct($path, $query = [], $headers = [], $body_stream = null) {
|
||||
parent::__construct($path, $query, $headers);
|
||||
$this->body_stream = $body_stream;
|
||||
public function method() {
|
||||
return $this->method;
|
||||
}
|
||||
}
|
||||
|
||||
class Response extends Message {
|
||||
private $status_code;
|
||||
public function path() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function query() {
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function __construct($path, $headers, $status_code) {
|
||||
parent::__construct($path, [], $headers);
|
||||
$this->status_code = $status_code;
|
||||
public function headers() {
|
||||
return $this->headers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace AWS\CRT\HTTP;
|
||||
|
||||
class Request extends Message {
|
||||
private $body_stream = null;
|
||||
|
||||
public function __construct($method, $path, $query = [], $headers = null, $body_stream = null) {
|
||||
parent::__construct($method, $path, $query, $headers);
|
||||
$this->body_stream = $body_stream;
|
||||
}
|
||||
|
||||
public static function marshall($request) {
|
||||
return parent::marshall($request);
|
||||
}
|
||||
|
||||
public static function unmarshall($buf) {
|
||||
return parent::_unmarshall($buf, Request::class);
|
||||
}
|
||||
|
||||
public function body_stream() {
|
||||
return $this->body_stream;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace AWS\CRT\HTTP;
|
||||
|
||||
class Response extends Message {
|
||||
private $status_code;
|
||||
|
||||
public function __construct($method, $path, $query, $headers, $status_code) {
|
||||
parent::__construct($method, $path, $query, $headers);
|
||||
$this->status_code = $status_code;
|
||||
}
|
||||
|
||||
public static function marshall($response) {
|
||||
return parent::marshall($response);
|
||||
}
|
||||
|
||||
public static function unmarshall($buf) {
|
||||
return parent::_unmarshall($buf, Response::class);
|
||||
}
|
||||
|
||||
public function status_code() {
|
||||
return $this->status_code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace AWS\CRT\Internal;
|
||||
|
||||
final class Encoding {
|
||||
public static function readString(&$buffer) {
|
||||
list($len, $str) = self::decodeString($buffer);
|
||||
// Advance by sizeof(length) + strlen(str)
|
||||
$buffer = substr($buffer, 4 + $len);
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function readStrings($buffer) {
|
||||
$strings = [];
|
||||
while (strlen($buffer)) {
|
||||
$strings []= self::readString($buffer);
|
||||
}
|
||||
return $strings;
|
||||
}
|
||||
|
||||
public static function decodeString($buffer) {
|
||||
$len = unpack("N", $buffer)[1];
|
||||
$buffer = substr($buffer, 4);
|
||||
$str = unpack("a${len}", $buffer)[1];
|
||||
return [$len, $str];
|
||||
}
|
||||
|
||||
public static function encodeString($str) {
|
||||
return pack("Na*", strlen($str), $str);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
use AWS\CRT\HTTP\Message;
|
||||
use AWS\CRT\HTTP\Headers;
|
||||
use AWS\CRT\HTTP\Request;
|
||||
use AWS\CRT\HTTP\Response;
|
||||
|
||||
require_once('common.inc');
|
||||
|
||||
final class HttpMessageTest extends CrtTestCase {
|
||||
public function testHeaders() {
|
||||
$headers = new Headers();
|
||||
$this->assertSame(0, $headers->count());
|
||||
}
|
||||
|
||||
public function testHeadersMarshalling() {
|
||||
$headers_array = [
|
||||
"host" => "s3.amazonaws.com",
|
||||
"test" => "this is a test header value"
|
||||
];
|
||||
$headers = new Headers($headers_array);
|
||||
$this->assertSame(2, $headers->count());
|
||||
$this->assertSame($headers_array['host'], $headers->get('host'));
|
||||
$this->assertSame($headers_array['test'], $headers->get('test'));
|
||||
$buffer = Headers::marshall($headers);
|
||||
$headers_copy = Headers::unmarshall($buffer);
|
||||
$this->assertSame(2, $headers_copy->count());
|
||||
$this->assertSame($headers_array['host'], $headers_copy->get('host'));
|
||||
$this->assertSame($headers_array['test'], $headers_copy->get('test'));
|
||||
}
|
||||
|
||||
private function assertMessagesMatch($a, $b) {
|
||||
$this->assertSame($a->method(), $b->method());
|
||||
$this->assertSame($a->path(), $b->path());
|
||||
$this->assertSame($a->query(), $b->query());
|
||||
$this->assertSame($a->headers()->toArray(), $b->headers()->toArray());
|
||||
}
|
||||
|
||||
public function testRequestMarshalling() {
|
||||
$headers_array = [
|
||||
"host" => "s3.amazonaws.com",
|
||||
"test" => "this is a test header value"
|
||||
];
|
||||
$headers = new Headers($headers_array);
|
||||
$method = "GET";
|
||||
$path = "/index.php";
|
||||
$query = [];
|
||||
|
||||
$msg = new Request($method, $path, $query, $headers);
|
||||
$msg_buf = Request::marshall($msg);
|
||||
$msg_copy = Request::unmarshall($msg_buf);
|
||||
|
||||
$this->assertMessagesMatch($msg, $msg_copy);
|
||||
}
|
||||
|
||||
public function testRequestMarshallingWithQueryParams() {
|
||||
$headers_array = [
|
||||
"host" => "s3.amazonaws.com",
|
||||
"test" => "this is a test header value"
|
||||
];
|
||||
$headers = new Headers($headers_array);
|
||||
$method = "GET";
|
||||
$path = "/index.php";
|
||||
$query = [
|
||||
'request' => '1',
|
||||
'test' => 'true',
|
||||
'answer' => '42',
|
||||
'foo' => 'bar',
|
||||
];
|
||||
|
||||
$msg = new Request($method, $path, $query, $headers);
|
||||
$msg_buf = Request::marshall($msg);
|
||||
$msg_copy = Request::unmarshall($msg_buf);
|
||||
|
||||
$this->assertMessagesMatch($msg, $msg_copy);
|
||||
}
|
||||
|
||||
public function testResponseMarshalling() {
|
||||
$headers_array = [
|
||||
"content-length" => "42",
|
||||
"test" => "this is a test header value"
|
||||
];
|
||||
$headers = new Headers($headers_array);
|
||||
$method = "GET";
|
||||
$path = "/index.php";
|
||||
$query = [
|
||||
'response' => '1'
|
||||
];
|
||||
|
||||
$msg = new Response($method, $path, $query, $headers, 400);
|
||||
$msg_buf = Request::marshall($msg);
|
||||
$msg_copy = Request::unmarshall($msg_buf);
|
||||
|
||||
$this->assertMessagesMatch($msg, $msg_copy);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user