lithium\net\http\Message
Extends
lithium\net\Message
Base class for lithium\net\http\Request
and lithium\net\http\Response
. Implements basic
protocol handling for HTTP-based transactions.
Subclasses
Source
class Message extends \lithium\net\Message {
/**
* The full protocol: HTTP/1.1
*
* @var string
*/
public $protocol = null;
/**
* Specification version number
*
* @var string
*/
public $version = '1.1';
/**
* HTTP headers
*
* @var array
*/
public $headers = [];
/**
* Content-Type
*
* @var string
*/
protected $_type = null;
/**
* Classes used by `Message` and its subclasses.
*
* @var array
*/
protected $_classes = [
'media' => 'lithium\net\http\Media',
'auth' => 'lithium\net\http\Auth'
];
/**
* Constructor. Adds config values to the public properties when a new object is created.
*
* @see lithium\net\Message::__construct()
* @param array $config The available configuration options are the following. Further
* options are inherited from the parent class.
* - `'protocol'` _string_: Defaults to `null`.
* - `'version'` _string_: Defaults to `'1.1'`.
* - `'scheme'` _string_: Overridden and defaulting to `'http'`.
* - `'headers'` _array_: Defaults to `array()`.
* @return void
*/
public function __construct(array $config = []) {
$defaults = [
'protocol' => null,
'version' => '1.1',
'scheme' => 'http',
'headers' => []
];
$config += $defaults;
foreach (array_intersect_key(array_filter($config), $defaults) as $key => $value) {
$this->{$key} = $value;
}
parent::__construct($config);
if (strpos($this->host, '/') !== false) {
list($this->host, $this->path) = explode('/', $this->host, 2);
}
$this->path = str_replace('//', '/', "/{$this->path}");
$this->protocol = $this->protocol ?: "HTTP/{$this->version}";
}
/**
* Adds, gets or removes one or multiple headers at the same time.
*
* Header names are not normalized and their casing left untouched. When
* headers are retrieved no sorting takes place. This behavior is inline
* with the specification which states header names should be treated in
* a case-insensitive way. Sorting is suggested but not required.
*
* ```
* // Get single or multiple headers.
* $request->headers('Content-Type'); // returns 'text/plain'
* $request->headers(); // returns ['Content-Type: text/plain', ... ]
*
* // Set single or multiple headers.
* $request->headers('Content-Type', 'text/plain');
* $request->headers(['Content-Type' => 'text/plain', ...]);
*
* // Alternatively use full header line.
* $request->headers('Content-Type: text/plain');
* $request->headers(['Content-Type: text/plain', ...]);
*
* // Removing single or multiple headers.
* $request->headers('Content-Type', false);
* $request->headers(['Content-Type' => false, ...]);
* ```
*
* Certain header fields support multiple values. These can be separated by
* comma or alternatively the header repeated for each value in the list.
*
* When explicitly adding a value to an already existing header (that is when
* $replace is `false`) an array with those values is kept/created internally.
* Later when retrieving headers the header will be repeated for each value.
*
* Note: Multiple headers of the same name are only valid if the values of
* that header can be separated by comma as defined in section 4.2 of RFC2616.
*
* ```
* // Replace single or multiple headers
* $request->headers('Cache-Control', 'no-store');
* $request->headers(['Cache-Control' => 'public']);
* $request->headers('Cache-Control'); // returns 'public'
*
* // Merging with existing array headers.
* // Note that new elements are just appended and no sorting takes place.
* $request->headers('Cache-Control', 'no-store');
* $request->headers('Cache-Control', 'no-cache', false);
* $request->headers();
* // returns ['Cache-Control: no-store', 'Cache-Control: no-cache']
*
* $request->headers('Cache-Control', 'no-store');
* $request->headers('Cache-Control', ['no-cache'], false);
* $request->headers();
* // returns ['Cache-Control: no-store', 'Cache-Control: no-cache']
*
* $request->headers('Cache-Control', 'max-age=0');
* $request->headers('Cache-Control', 'no-store, no-cache');
* $request->headers();
* // returns ['Cache-Control: no-store, no-cache']
* ```
*
* @link http://www.ietf.org/rfc/rfc2616.txt Section 4.2 Message Headers
* @param string|array $key A header name, a full header line (`'<key>: <value>'`), or an array
* of headers to set in `key => value` form.
* @param mixed $value A value to set if `$key` is a string.
* It can be an array to set multiple headers with the same key.
* If `null`, returns the value of the header corresponding to `$key`.
* If `false`, it unsets the header corresponding to `$key`.
* @param boolean $replace Whether to override or add alongside any existing header with
* the same name.
* @return mixed When called with just $key provided, the value of a single header or an array
* of values in case there is multiple headers with this key.
* When calling the method without any arguments, an array of compiled headers in the
* form `array('<key>: <value>', ...)` is returned. All set and replace operations
* return no value for performance reasons.
*/
public function headers($key = null, $value = null, $replace = true) {
if ($key === null && $value === null) {
$headers = [];
foreach ($this->headers as $key => $value) {
if (is_scalar($value)) {
$headers[] = "{$key}: {$value}";
continue;
}
foreach ($value as $val) {
$headers[] = "{$key}: {$val}";
}
}
return $headers;
}
if ($value === null && is_string($key) && strpos($key, ':') === false) {
return isset($this->headers[$key]) ? $this->headers[$key] : null;
}
if (is_string($key)) {
if (strpos($key, ':') !== false && preg_match('/(.*?):(.+)/', $key, $match)) {
$key = $match[1];
$value = trim($match[2]);
} elseif ($value === false) {
unset($this->headers[$key]);
return;
}
if ($replace || !isset($this->headers[$key])) {
$this->headers[$key] = $value;
} elseif ($value !== $this->headers[$key]) {
$this->headers[$key] = (array) $this->headers[$key];
if (is_string($value)) {
$this->headers[$key][] = $value;
} else {
$this->headers[$key] = array_merge($this->headers[$key], $value);
}
}
} else {
$replace = ($value === false) ? $value : $replace;
foreach ((array) $key as $header => $value) {
if (is_string($header)) {
$this->headers($header, $value, $replace);
continue;
}
$this->headers($value, null, $replace);
}
}
}
/**
* Sets/gets the content type.
*
* @param string $type A full content type i.e. `'application/json'` or simple name `'json'`
* @return string A simple content type name, i.e. `'html'`, `'xml'`, `'json'`, etc., depending
* on the content type of the request.
*/
public function type($type = null) {
if ($type === false) {
unset($this->headers['Content-Type']);
$this->_type = false;
return;
}
$media = $this->_classes['media'];
if (!$type && $this->_type) {
return $this->_type;
}
$headers = $this->headers + ['Content-Type' => null];
$type = $type ?: $headers['Content-Type'];
if (!$type) {
return;
}
$header = $type;
if (!$data = $media::type($type)) {
$this->headers('Content-Type', $type);
return ($this->_type = $type);
}
if (is_string($data)) {
$type = $data;
} elseif (!empty($data['content'])) {
$header = is_string($data['content']) ? $data['content'] : reset($data['content']);
}
$this->headers('Content-Type', $header);
return ($this->_type = $type);
}
/**
* Add data to and compile the HTTP message body, optionally encoding or decoding its parts
* according to content type.
*
* @param mixed $data
* @param array $options
* - `'buffer'` _integer_: split the body string
* - `'encode'` _boolean_: encode the body based on the content type
* - `'decode'` _boolean_: decode the body based on the content type
* @return array
*/
public function body($data = null, $options = []) {
$default = ['buffer' => null, 'encode' => false, 'decode' => false];
$options += $default;
if ($data !== null) {
$this->body = array_merge((array) $this->body, (array) $data);
}
$body = $this->body;
if (empty($options['buffer']) && $body === null) {
return "";
}
if ($options['encode']) {
$body = $this->_encode($body);
}
$body = is_string($body) ? $body : join("\r\n", (array) $body);
if ($options['decode']) {
$body = $this->_decode($body);
}
return ($options['buffer']) ? str_split($body, $options['buffer']) : $body;
}
/**
* Encode the body based on the content type
*
* @see lithium\net\http\Message::type()
* @param mixed $body
* @return string
*/
protected function _encode($body) {
$media = $this->_classes['media'];
if ($media::type($this->_type)) {
$encoded = $media::encode($this->_type, $body);
$body = $encoded !== null ? $encoded : $body;
}
return $body;
}
/**
* Decode the body based on the content type
*
* @see lithium\net\http\Message::type()
* @param string $body
* @return mixed
*/
protected function _decode($body) {
$media = $this->_classes['media'];
if ($media::type($this->_type)) {
$decoded = $media::decode($this->_type, $body);
$body = $decoded !== null ? $decoded : $body;
}
return $body;
}
}