Workerman-DNS/vendor/workerman/workerman/Protocols/Http.php

325 lines
10 KiB
PHP

<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Protocols\Http\Response;
use Workerman\Protocols\Http\Session;
use Workerman\Protocols\Websocket;
use Workerman\Worker;
/**
* Class Http.
* @package Workerman\Protocols
*/
class Http
{
/**
* Request class name.
*
* @var string
*/
protected static $_requestClass = 'Workerman\Protocols\Http\Request';
/**
* Upload tmp dir.
*
* @var string
*/
protected static $_uploadTmpDir = '';
/**
* Open cache.
*
* @var bool.
*/
protected static $_enableCache = true;
/**
* Get or set session name.
*
* @param string|null $name
* @return string
*/
public static function sessionName($name = null)
{
if ($name !== null && $name !== '') {
Session::$name = (string)$name;
}
return Session::$name;
}
/**
* Get or set the request class name.
*
* @param string|null $class_name
* @return string
*/
public static function requestClass($class_name = null)
{
if ($class_name) {
static::$_requestClass = $class_name;
}
return static::$_requestClass;
}
/**
* Enable or disable Cache.
*
* @param mixed $value
*/
public static function enableCache($value)
{
static::$_enableCache = (bool)$value;
}
/**
* Check the integrity of the package.
*
* @param string $recv_buffer
* @param TcpConnection $connection
* @return int
*/
public static function input($recv_buffer, TcpConnection $connection)
{
static $input = array();
if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) {
return $input[$recv_buffer];
}
$crlf_pos = \strpos($recv_buffer, "\r\n\r\n");
if (false === $crlf_pos) {
// Judge whether the package length exceeds the limit.
if ($recv_len = \strlen($recv_buffer) >= 16384) {
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
return 0;
}
return 0;
}
$head_len = $crlf_pos + 4;
$method = \strstr($recv_buffer, ' ', true);
if ($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD' || $method === 'DELETE') {
if (!isset($recv_buffer[512])) {
$input[$recv_buffer] = $head_len;
if (\count($input) > 512) {
unset($input[key($input)]);
}
}
return $head_len;
} else if ($method !== 'POST' && $method !== 'PUT' && $method !== 'PATCH') {
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
$header = \substr($recv_buffer, 0, $crlf_pos);
$length = false;
if ($pos = \strpos($header, "\r\nContent-Length: ")) {
$length = $head_len + (int)\substr($header, $pos + 18, 10);
} else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) {
$length = $head_len + $match[1];
}
if ($length !== false) {
if (!isset($recv_buffer[512])) {
$input[$recv_buffer] = $length;
if (\count($input) > 512) {
unset($input[key($input)]);
}
}
if ($length > $connection->maxPackageSize) {
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
return 0;
}
return $length;
}
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
/**
* Http decode.
*
* @param string $recv_buffer
* @param TcpConnection $connection
* @return \Workerman\Protocols\Http\Request
*/
public static function decode($recv_buffer, TcpConnection $connection)
{
static $requests = array();
$cacheable = static::$_enableCache && !isset($recv_buffer[512]);
if (true === $cacheable && isset($requests[$recv_buffer])) {
$request = $requests[$recv_buffer];
$request->connection = $connection;
$connection->__request = $request;
$request->properties = array();
return $request;
}
$request = new static::$_requestClass($recv_buffer);
$request->connection = $connection;
$connection->__request = $request;
if (true === $cacheable) {
$requests[$recv_buffer] = $request;
if (\count($requests) > 512) {
unset($requests[key($requests)]);
}
}
return $request;
}
/**
* Http encode.
*
* @param string|Response $response
* @param TcpConnection $connection
* @return string
*/
public static function encode($response, TcpConnection $connection)
{
if (isset($connection->__request)) {
$connection->__request->session = null;
$connection->__request->connection = null;
$connection->__request = null;
}
if (!\is_object($response)) {
$ext_header = '';
if (isset($connection->__header)) {
foreach ($connection->__header as $name => $value) {
if (\is_array($value)) {
foreach ($value as $item) {
$ext_header = "$name: $item\r\n";
}
} else {
$ext_header = "$name: $value\r\n";
}
}
unset($connection->__header);
}
$body_len = \strlen((string)$response);
return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response";
}
if (isset($connection->__header)) {
$response->withHeaders($connection->__header);
unset($connection->__header);
}
if (isset($response->file)) {
$file = $response->file['file'];
$offset = $response->file['offset'];
$length = $response->file['length'];
$file_size = (int)\filesize($file);
$body_len = $length > 0 ? $length : $file_size - $offset;
$response->withHeaders(array(
'Content-Length' => $body_len,
'Accept-Ranges' => 'bytes',
));
if ($offset || $length) {
$offset_end = $offset + $body_len - 1;
$response->header('Content-Range', "bytes $offset-$offset_end/$file_size");
}
if ($body_len < 2 * 1024 * 1024) {
$connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true);
return '';
}
$handler = \fopen($file, 'r');
if (false === $handler) {
$connection->close(new Response(403, null, '403 Forbidden'));
return '';
}
$connection->send((string)$response, true);
static::sendStream($connection, $handler, $offset, $length);
return '';
}
return (string)$response;
}
/**
* Send remainder of a stream to client.
*
* @param TcpConnection $connection
* @param resource $handler
* @param int $offset
* @param int $length
*/
protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0)
{
$connection->bufferFull = false;
if ($offset !== 0) {
\fseek($handler, $offset);
}
$offset_end = $offset + $length;
// Read file content from disk piece by piece and send to client.
$do_write = function () use ($connection, $handler, $length, $offset_end) {
// Send buffer not full.
while ($connection->bufferFull === false) {
// Read from disk.
$size = 1024 * 1024;
if ($length !== 0) {
$tell = \ftell($handler);
$remain_size = $offset_end - $tell;
if ($remain_size <= 0) {
fclose($handler);
$connection->onBufferDrain = null;
return;
}
$size = $remain_size > $size ? $size : $remain_size;
}
$buffer = \fread($handler, $size);
// Read eof.
if ($buffer === '' || $buffer === false) {
fclose($handler);
$connection->onBufferDrain = null;
return;
}
$connection->send($buffer, true);
}
};
// Send buffer full.
$connection->onBufferFull = function ($connection) {
$connection->bufferFull = true;
};
// Send buffer drain.
$connection->onBufferDrain = function ($connection) use ($do_write) {
$connection->bufferFull = false;
$do_write();
};
$do_write();
}
/**
* Set or get uploadTmpDir.
*
* @return bool|string
*/
public static function uploadTmpDir($dir = null)
{
if (null !== $dir) {
static::$_uploadTmpDir = $dir;
}
if (static::$_uploadTmpDir === '') {
if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) {
static::$_uploadTmpDir = $upload_tmp_dir;
} else if ($upload_tmp_dir = \sys_get_temp_dir()) {
static::$_uploadTmpDir = $upload_tmp_dir;
}
}
return static::$_uploadTmpDir;
}
}