This commit is contained in:
2021-10-25 18:25:14 +08:00
parent 6605aae410
commit a9d106a1cf
48 changed files with 5090 additions and 0 deletions
+372
View File
@@ -0,0 +1,372 @@
<?php
namespace Http\Message;
class ContentType
{
const X = 'application/x-';
const OCTET_STREAM = 'application/octet-stream';
const PDF = 'application/pdf';
const AI = 'application/postscript';
const ATOM_XML = 'application/atom+xml';
const JS = 'application/ecmascript';
const EDI_X12 = 'application/EDI-X12';
const EDIFACT = 'application/EDIFACT';
const JSON = 'application/json';
const JAVASCRIPT = 'application/javascript';
const OGG = 'application/ogg';
const RDF = 'application/rdf+xml';
const RSS_XML = 'application/rss+xml';
const SOAP_XML = 'application/soap+xml';
const WOFF = 'application/font-woff';
const XHTML_XML = 'application/xhtml+xml';
const XML = 'application/xml';
const DTD = 'application/xml-dtd';
const XOP_XML = 'application/xop+xml';
const ZIP = 'application/zip';
const GZIP = 'application/gzip';
const XLS = 'application/x-xls';
const X_001 = 'application/x-001';
const X_301 = 'application/x-301';
const X_906 = 'application/x-906';
const A11 = 'application/x-a11';
const AWF = 'application/vnd.adobe.workflow';
const BMP = 'application/x-bmp';
const C4T = 'application/x-c4t';
const CAL = 'application/x-cals';
const CDF = 'application/x-netcdf';
const CEL = 'application/x-cel';
const CG4 = 'application/x-g4';
const CIT = 'application/x-cit';
const BOT = 'application/x-bot';
const C90 = 'application/x-c90';
const CAT = 'application/vnd.ms-pki.seccat';
const CDR = 'application/x-cdr';
const CER = 'application/x-x509-ca-cert';
const CGM = 'application/x-cgm';
const CMX = 'application/x-cmx';
const CRL = 'application/pkix-crl';
const CSI = 'application/x-csi';
const CUT = 'application/x-cut';
const DBM = 'application/x-dbm';
const CMP = 'application/x-cmp';
const COT = 'application/x-cot';
const CRT = 'application/x-x509-ca-cert';
const DBF = 'application/x-dbf';
const DBX = 'application/x-dbx';
const DCX = 'application/x-dcx';
const DGN = 'application/x-dgn';
const DLL = 'application/x-msdownload';
const DOT = 'application/msword';
const DER = 'application/x-x509-ca-cert';
const DIB = 'application/x-dib';
const DOC = 'application/msword';
const DRW = 'application/x-drw';
const DWF = 'application/x-dwf';
const DXB = 'application/x-dxb';
const EDN = 'application/vnd.adobe.edn';
const DWG = 'application/x-dwg';
const DXF = 'application/x-dxf';
const EMF = 'application/x-emf';
const EPI = 'application/x-epi';
const EPS = 'application/postscript';
const EXE = 'application/x-msdownload';
const FDF = 'application/vnd.fdf';
const X_EPS = 'application/x-ps';
const ETD = 'application/x-ebx';
const FIF = 'application/fractals';
const FRM = 'application/x-frm';
const GBR = 'application/x-gbr';
const G4 = 'application/x-g4';
const GL2 = 'application/x-gl2';
const HGL = 'application/x-hgl';
const HPG = 'application/x-hpgl';
const HQX = 'application/mac-binhex40';
const HTA = 'application/hta';
const GP4 = 'application/x-gp4';
const HMR = 'application/x-hmr';
const HPL = 'application/x-hpl';
const HRF = 'application/x-hrf';
const ICB = 'application/x-icb';
const ICO = 'application/x-ico';
const IG4 = 'application/x-g4';
const III = 'application/x-iphone';
const INS = 'application/x-internet-signup';
const IFF = 'application/x-iff';
const IGS = 'application/x-igs';
const IMG = 'application/x-img';
const ISP = 'application/x-internet-signup';
const JPE = 'application/x-jpe';
const X_JAVASCRIPT = 'application/x-javascript';
const JPG = 'application/x-jpg';
const LAR = 'application/x-laplayer-reg';
const LATEX = 'application/x-latex';
const LBM = 'application/x-lbm';
const LS = 'application/x-javascript';
const LTR = 'application/x-ltr';
const MAN = 'application/x-troff-man';
const MDB = 'application/msaccess';
const MAC = 'application/x-mac';
const X_MDB = 'application/x-mdb';
const MFP = 'application/x-shockwave-flash';
const MI = 'application/x-mi';
const MIL = 'application/x-mil';
const MOCHA = 'application/x-javascript';
const MPD = 'application/vnd.ms-project';
const MPP = 'application/vnd.ms-project';
const MPT = 'application/vnd.ms-project';
const MPW = 'application/vnd.ms-project';
const MPX = 'application/vnd.ms-project';
const MXP = 'application/x-mmxp';
const NRF = 'application/x-nrf';
const OUT = 'application/x-out';
const P12 = 'application/x-pkcs12';
const P7C = 'application/pkcs7-mime';
const P7R = 'application/x-pkcs7-certreqresp';
const PC5 = 'application/x-pc5';
const PCL = 'application/x-pcl';
const PDX = 'application/vnd.adobe.pdx';
const PGL = 'application/x-pgl';
const PKO = 'application/vnd.ms-pki.pko';
const P10 = 'application/pkcs10';
const P7B = 'application/x-pkcs7-certificates';
const P7M = 'application/pkcs7-mime';
const P7S = 'application/pkcs7-signature';
const PCI = 'application/x-pci';
const PCX = 'application/x-pcx';
const PFX = 'application/x-pkcs12';
const PIC = 'application/x-pic';
const PL = 'application/x-perl';
const PLT = 'application/x-plt';
const PNG = 'application/x-png';
const PPA = 'application/vnd.ms-powerpoint';
const PPS = 'application/vnd.ms-powerpoint';
const X_PPT = 'application/x-ppt';
const PRF = 'application/pics-rules';
const PRT = 'application/x-prt';
const PS = 'application/postscript';
const PWZ = 'application/vnd.ms-powerpoint';
const RA = 'audio/vnd.rn-realaudio';
const RAS = 'application/x-ras';
const POT = 'application/vnd.ms-powerpoint';
const PPM = 'application/x-ppm';
const PPT = 'application/vnd.ms-powerpoint';
const PR = 'application/x-pr';
const PRN = 'application/x-prn';
const X_PS = 'application/x-ps';
const PTN = 'application/x-ptn';
const RED = 'application/x-red';
const RJS = 'application/vnd.rn-realsystem-rjs';
const RLC = 'application/x-rlc';
const RM = 'application/vnd.rn-realmedia';
const RAT = 'application/rat-file';
const REC = 'application/vnd.rn-recording';
const RGB = 'application/x-rgb';
const RJT = 'application/vnd.rn-realsystem-rjt';
const RLE = 'application/x-rle';
const RMF = 'application/vnd.adobe.rmf';
const RMJ = 'application/vnd.rn-realsystem-rmj';
const RMP = 'application/vnd.rn-rn_music_package';
const RMVB = 'application/vnd.rn-realmedia-vbr';
const RNX = 'application/vnd.rn-realplayer';
const RPM = 'audio/x-pn-realaudio-plugin';
const RMS = 'application/vnd.rn-realmedia-secure';
const RMX = 'application/vnd.rn-realsystem-rmx';
const RSML = 'application/vnd.rn-rsml';
const RTF = 'application/msword';
const RV = 'video/vnd.rn-realvideo';
const SAT = 'application/x-sat';
const SDW = 'application/x-sdw';
const SLB = 'application/x-slb';
const X_RTF = 'application/x-rtf';
const SAM = 'application/x-sam';
const SDP = 'application/sdp';
const SIT = 'application/x-stuffit';
const SLD = 'application/x-sld';
const SMI = 'application/smil';
const SMK = 'application/x-smk';
const SMIL = 'application/smil';
const SPC = 'application/x-pkcs7-certificates';
const SPL = 'application/futuresplash';
const SSM = 'application/streamingmedia';
const STL = 'application/vnd.ms-pki.stl';
const SST = 'application/vnd.ms-pki.certstore';
const TDF = 'application/x-tdf';
const TGA = 'application/x-tga';
const STY = 'application/x-sty';
const SWF = 'application/x-shockwave-flash';
const TG4 = 'application/x-tg4';
const TIF = 'application/x-tif';
const VDX = 'application/vnd.visio';
const VPG = 'application/x-vpeg005';
const VSD = 'application/x-vsd';
const VST = 'application/vnd.visio';
const VSW = 'application/vnd.visio';
const VTX = 'application/vnd.visio';
const TORRENT = 'application/x-bittorrent';
const VDA = 'application/x-vda';
const VND_VISIO = 'application/vnd.visio';
const VSS = 'application/vnd.visio';
const X_VST = 'application/x-vst';
const VSX = 'application/vnd.visio';
const WB1 = 'application/x-wb1';
const WB3 = 'application/x-wb3';
const WIZ = 'application/msword';
const WK4 = 'application/x-wk4';
const WKS = 'application/x-wks';
const WB2 = 'application/x-wb2';
const WK3 = 'application/x-wk3';
const WKQ = 'application/x-wkq';
const WMF = 'application/x-wmf';
const WMD = 'application/x-ms-wmd';
const WP6 = 'application/x-wp6';
const WPG = 'application/x-wpg';
const WQ1 = 'application/x-wq1';
const WRI = 'application/x-wri';
const WS = 'application/x-ws';
const WMZ = 'application/x-ms-wmz';
const WPD = 'application/x-wpd';
const WPL = 'application/vnd.ms-wpl';
const WR1 = 'application/x-wr1';
const WRK = 'application/x-wrk';
const WS2 = 'application/x-ws';
const XDP = 'application/vnd.adobe.xdp';
const XFD = 'application/vnd.adobe.xfd';
const XFDF = 'application/vnd.adobe.xfdf';
const VND_MS_EXCEL = 'application/vnd.ms-excel';
const XWD = 'application/x-xwd';
const SIS = 'application/vnd.symbian.install';
const X_T = 'application/x-x_t';
const APK = 'application/vnd.android.package-archive';
const X_B = 'application/x-x_b';
const SISX = 'application/vnd.symbian.install';
const IPA = 'application/vnd.iphone';
const XAP = 'application/x-silverlight-app';
const XLW = 'application/x-xlw';
const XPL = 'audio/scpls';
const ANV = 'application/x-anv';
const UIN = 'application/x-icq';
const H323 = 'text/h323';
const BIZ = 'text/xml';
const CML = 'text/xml';
const ASA = 'text/asa';
const ASP = 'text/asp';
const CSS = 'text/css';
const CSV = 'text/csv';
const DCD = 'text/xml';
const X_DTD = 'text/xml';
const ENT = 'text/xml';
const FO = 'text/xml';
const HTC = 'text/x-component';
const HTML = 'text/html';
const HTX = 'text/html';
const HTM = 'text/html';
const HTT = 'text/webviewhtml';
const JSP = 'text/html';
const MATH = 'text/xml';
const MML = 'text/xml';
const MTX = 'text/xml';
const PLG = 'text/html';
const X_RDF = 'text/xml';
const RT = 'text/vnd.rn-realtext';
const SOL = 'text/plain';
const SPP = 'text/xml';
const STM = 'text/html';
const SVG = 'text/xml';
const TLD = 'text/xml';
const TXT = 'text/plain纯文字内容';
const ULS = 'text/iuls';
const VML = 'text/xml';
const TSD = 'text/xml';
const VCF = 'text/x-vcard';
const VXML = 'text/xml';
const WML = 'text/vnd.wap.wml';
const WSDL = 'text/xml';
const WSC = 'text/scriptlet';
const XDR = 'text/xml';
const XQL = 'text/xml';
const XSD = 'text/xml';
const XSLT = 'text/xml';
const X_XML = 'text/xml';
const XQ = 'text/xml';
const XQUERY = 'text/xml';
const XSL = 'text/xml';
const XHTML = 'text/html';
const ODC = 'text/x-ms-odc';
const R3T = 'text/vnd.rn-realtext3d';
const SOR = 'text/plain';
const ACP = 'audio/x-mei-aac';
const AIF = 'audio/aiff';
const AIFF = 'audio/aiff';
const AIFC = 'audio/aiff';
const AU = 'audio/basic';
const LA1 = 'audio/x-liquid-file';
const LAVS = 'audio/x-liquid-secure';
const LMSFF = 'audio/x-la-lms';
const M3U = 'audio/mpegurl';
const MIDI = 'audio/mid';
const MID = 'audio/mid';
const MP2 = 'audio/mp2';
const MP3 = 'audio/mp3';
const MP4 = 'audio/mp4';
const MND = 'audio/x-musicnet-download';
const MP1 = 'audio/mp1';
const MNS = 'audio/x-musicnet-stream';
const MPGA = 'audio/rn-mpeg';
const PLS = 'audio/scpls';
const RAM = 'audio/x-pn-realaudio';
const RMI = 'audio/mid';
const RMM = 'audio/x-pn-realaudio';
const SND = 'audio/basic';
const WAV = 'audio/wav';
const WAX = 'audio/x-ms-wax';
const WMA = 'audio/x-ms-wma';
const ASF = 'video/x-ms-asf';
const ASX = 'video/x-ms-asf';
const AVI = 'video/avi';
const IVF = 'video/x-ivf';
const M1V = 'video/x-mpeg';
const M2V = 'video/x-mpeg';
const M4E = 'video/mpeg4';
const MOVIE = 'video/x-sgi-movie';
const MP2V = 'video/mpeg';
const X_MP4 = 'video/mpeg4';
const MPA = 'video/x-mpg';
const MPE = 'video/x-mpeg';
const MPG = 'video/mpg';
const MPEG = 'video/mpg';
const MPS = 'video/x-mpeg';
const MPV = 'video/mpg';
const MPV2 = 'video/mpeg';
const WM = 'video/x-ms-wm';
const WMV = 'video/x-ms-wmv';
const WMX = 'video/x-ms-wmx';
const WVX = 'video/x-ms-wvx';
const TIFF = 'image/tiff';
const FAX = 'image/fax';
const GIF = 'image/gif';
const ICON = 'image/x-icon';
const JFIF = 'image/jpeg';
const X_JPE = 'image/jpeg';
const JPEG = 'image/jpeg';
const X_JPG = 'image/jpeg';
const NET = 'image/pnetvue';
const X_PNG = 'image/png';
const RP = 'image/vnd.rn-realpix';
const X_TIF = 'image/tiff';
const X_TIFF = 'image/tiff';
const WBMP = 'image/vnd.wap.wbmp';
const EML = 'message/rfc822';
const MHT = 'message/rfc822';
const MHTML = 'message/rfc822';
const NWS = 'message/rfc822';
const D_907 = 'drawing/907';
const SLK = 'drawing/x-slk';
const TOP = 'drawing/x-top';
const JAVA_CLASS = 'java/*';
const JAVA = 'java/*';
const X_DWF = 'Model/vnd.dwf';
}
+306
View File
@@ -0,0 +1,306 @@
<?php
namespace Http\Message;
use JetBrains\PhpStorm\Pure;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
*
*/
trait Message
{
/**
* @var string
*/
protected string $version;
/**
* @var StreamInterface
*/
protected StreamInterface $stream;
/**
* @var array
*/
protected array $headers = [];
/**
* @var array|null
*/
protected ?array $cookieParams = [];
/**
* @return string
*/
public function getProtocolVersion(): string
{
return $this->version;
}
/**
* @param $version
* @return static
*/
public function withProtocolVersion($version): static
{
$this->version = $version;
return $this;
}
/**
* @return array
*/
public function getHeaders(): array
{
return $this->headers;
}
/**
* @param $name
* @return bool
*/
public function hasHeader($name): bool
{
return array_key_exists($name, $this->headers);
}
/**
* @param $name
* @return string|array|null
*/
#[Pure] public function getHeader($name): string|null|array
{
if (!$this->hasHeader($name)) {
return null;
}
return $this->headers[$name];
}
/**
* @return array
*/
public function parse_curl_header(): array
{
$_headers = [];
foreach ($this->headers as $key => $val) {
$_headers[] = $key . ': ' . implode(';', $val);
}
return $_headers;
}
/**
* @throws \Exception
*/
public function withData(string $headerString): static
{
[$headers, $body] = explode("\r\n\r\n", $headerString);
$this->stream = new Stream($body);
return $this->slip_headers($headers);
}
/**
* @param $headers
* @return $this
* @throws \Exception
*/
private function slip_headers($headers): static
{
$headers = explode("\r\n", $headers);
$this->resolve_status(array_shift($headers));
foreach ($headers as $header) {
[$key, $value] = explode(': ', $header);
$this->withHeader($key, $value);
}
return $this;
}
/**
* @param string $protocol
*/
private function resolve_status(string $protocol)
{
if ($this instanceof ResponseInterface) {
[$sch, $status, $message] = explode(' ', $protocol);
[$sch, $protocolVersion] = explode('/', $sch);
$this->withProtocolVersion($protocolVersion)
->withStatus(intval($status));
}
}
/**
* @param $key
* @param $value
*/
private function addRequestHeader($key, $value)
{
$this->headers[$key] = [$value];
}
/**
* @param $name
* @return string|null
*/
#[Pure] public function getHeaderLine($name): string|null
{
if ($this->hasHeader($name)) {
return implode(';', $this->headers[$name]);
}
return null;
}
/**
* @return string|null
*/
#[Pure] public function getContentType(): ?string
{
return $this->getHeaderLine('Content-Type');
}
/**
* @param $name
* @param $value
* @return static
*/
public function withHeader($name, $value): static
{
if (!is_array($value)) {
$value = [$value];
}
$this->headers[$name] = $value;
return $this;
}
/**
* @param array $headers
* @return static
*/
public function withHeaders(array $headers): static
{
$this->headers = $headers;
return $this;
}
/**
* @param $name
* @param $value
* @return static
* @throws
*/
public function withAddedHeader($name, $value): static
{
if (!array_key_exists($name, $this->headers)) {
throw new \Exception('Headers `' . $name . '` not exists.');
}
$this->headers[$name][] = $value;
return $this;
}
/**
* @param $name
* @return static
*/
public function withoutHeader($name): static
{
unset($this->headers[$name]);
return $this;
}
/**
* @return null|array
*/
public function getCookieParams(): ?array
{
return $this->cookieParams;
}
/**
* @param array|null $cookies
* @return static
*/
public function withCookieParams(?array $cookies): static
{
$this->cookieParams = $cookies;
return $this;
}
/**
* @return StreamInterface
*/
public function getBody(): StreamInterface
{
return $this->stream;
}
/**
* @param StreamInterface $body
* @return static
*/
public function withBody(StreamInterface $body): static
{
$this->stream = $body;
return $this;
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlAllowOrigin(): ?string
{
return $this->getHeaderLine('Access-Control-Allow-Origin');
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlAllowHeaders(): ?string
{
return $this->getHeaderLine('Access-Control-Allow-Headers');
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlRequestMethod(): ?string
{
return $this->getHeaderLine('Access-Control-Request-Method');
}
protected function setStore($key, callable $callback)
{
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
namespace Http\Message;
use Http\OnDownloadInterface;
class OnDownload extends Response implements OnDownloadInterface
{
use Message;
private string $path;
private bool $isChunk;
private int $size;
private int $offset;
const IMAGES = [
'png' => 'image/png',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
'svg' => 'image/svg+xml',
];
/**
* @param string $path
* @param false $isChunk
* @param int $size
* @param int $offset
* @return $this
*/
public function path(string $path, bool $isChunk = false, int $size = -1, int $offset = 0): OnDownload
{
$this->path = $path;
$this->isChunk = $isChunk;
$this->size = $size;
$this->offset = $offset;
return $this->emitter();
}
/**
* @return $this
*/
public function emitter(): static
{
$explode = explode('/', $this->path);
$this->withHeader('Pragma', 'public');
$this->withHeader('Expires', '0');
$this->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
$this->withHeader('Content-Disposition', 'attachment;filename=' . end($explode));
$this->withHeader('Content-Type', $type = get_file_extension($this->path));
if (!in_array($type, self::IMAGES)) {
$this->withHeader('Content-Transfer-Encoding', 'binary');
}
if ($this->isChunk) {
$resource = fopen($this->path, 'r');
$state = fstat($resource);
$this->withHeader('Content-length', $state['size']);
}
return $this;
}
/**
* @param \Swoole\Http\Response $response
*/
public function dispatch(mixed $response)
{
if (!$this->isChunk) {
$response->sendfile($this->path);
} else {
$this->chunk($response);
}
}
/**
* @param \Swoole\Http\Response $response
*/
private function chunk(\Swoole\Http\Response $response): void
{
$resource = fopen($this->path, 'r');
$state = fstat($resource);
$offset = $this->offset;
while ($file = fread($resource, $this->size)) {
$response->write($file);
fseek($resource, $offset);
if ($offset >= $state['size']) {
break;
}
$offset += $this->size;
}
$response->end();
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Http\Message;
use Kiri\Core\Xml;
class Parse
{
/**
* @param $content
* @return mixed
* @throws \Exception
*/
public static function data($content): mixed
{
if (empty($content)) {
return null;
}
if (is_bool($content) || is_numeric($content)) {
return $content;
}
$start = substr($content, 0, 1);
return match ($start) {
'<' => Xml::toArray($content),
'[', '{' => json_decode($content, true),
default => call_user_func(function () use ($content) {
parse_str($content, $array);
return $array;
})
};
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Http\Message;
use BadMethodCallException;
use Http\Handler\AuthIdentity;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
*
*/
class Request implements RequestInterface
{
use Message;
/**
* @var UriInterface
*/
protected UriInterface $uriInterface;
/**
* @var string
*/
protected string $method;
/**
* @var AuthIdentity|null
*/
public ?AuthIdentity $authority = null;
/**
* @param AuthIdentity|null $authIdentity
*/
public function setAuthority(?AuthIdentity $authIdentity): void
{
$this->authority = $authIdentity;
}
/**
* @return string
*/
public function getRequestTarget(): string
{
throw new BadMethodCallException('Not Accomplish Method.');
}
/**
* @param mixed $requestTarget
* @return static
*/
public function withRequestTarget($requestTarget): static
{
throw new BadMethodCallException('Not Accomplish Method.');
}
/**
* @return string
*/
public function getMethod(): string
{
return $this->method;
}
/**
* @param string $method
* @return RequestInterface
*/
public function withMethod($method): RequestInterface
{
$this->method = $method;
return $this;
}
/**
* @param string $method
* @return bool
*/
public function isMethod(string $method): bool
{
return $this->method == $method;
}
/**
* @return UriInterface
*/
public function getUri(): UriInterface
{
return $this->uriInterface;
}
/**
* @param UriInterface $uri
* @param false $preserveHost
* @return $this|Request
*/
public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
{
$this->uriInterface = $uri;
return $this;
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
namespace Http\Message;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Core\Help;
use Http\Constrict\ResponseInterface;
use Http\OnDownloadInterface;
/**
*
*/
class Response implements ResponseInterface
{
use Message;
const CONTENT_TYPE_HTML = 'text/html';
protected string $charset = 'utf8';
protected int $statusCode = 200;
protected string $reasonPhrase = '';
/**
* __construct
*/
public function __construct()
{
$this->stream = new Stream();
}
/**
* @return int
*/
public function getStatusCode(): int
{
return $this->statusCode;
}
/**
* @param int $code
* @param string $reasonPhrase
* @return $this|Response
*/
public function withStatus($code, $reasonPhrase = ''): static
{
$this->statusCode = $code;
$this->reasonPhrase = $reasonPhrase;
return $this;
}
/**
* @return string
*/
public function getReasonPhrase(): string
{
return $this->reasonPhrase;
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlAllowOrigin(): ?string
{
return $this->getHeaderLine('Access-Control-Allow-Origin');
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlAllowHeaders(): ?string
{
return $this->getHeaderLine('Access-Control-Allow-Headers');
}
/**
* @return string|null
*/
#[Pure] public function getAccessControlRequestMethod(): ?string
{
return $this->getHeaderLine('Access-Control-Request-Method');
}
/**
* @param string $type
* @return Response
*/
public function withContentType(string $type): static
{
return $this->withHeader('Content-Type', $type);
}
/**
* @return bool
*/
#[Pure] public function hasContentType(): bool
{
return $this->hasHeader('Content-Type');
}
/**
* @param string|null $value
* @return Response
*/
public function withAccessControlAllowHeaders(?string $value): static
{
return $this->withHeader('Access-Control-Allow-Headers', $value);
}
/**
* @param string|null $value
* @return Response
*/
public function withAccessControlRequestMethod(?string $value): static
{
return $this->withHeader('Access-Control-Request-Method', $value);
}
/**
* @param string|null $value
* @return Response
*/
public function withAccessControlAllowOrigin(?string $value): static
{
return $this->withHeader('Access-Control-Allow-Origin', $value);
}
/**
* @param $data
* @param string $contentType
* @return static
*/
public function json($data, string $contentType = 'application/json'): static
{
$this->stream->write(json_encode($data));
return $this->withContentType($contentType);
}
/**
* @param $data
* @param string $contentType
* @return static
*/
public function html($data, string $contentType = 'text/html'): static
{
if (!is_string($data)) {
$data = json_encode($data);
}
$this->stream->write((string)$data);
return $this->withContentType($contentType);
}
/**
* @param $data
* @param string $contentType
* @return static
*/
public function xml($data, string $contentType = 'application/xml'): static
{
$this->stream->write(Help::toXml($data));
return $this->withContentType($contentType);
}
/**
* @param string $charset
* @return $this
*/
public function withCharset(string $charset): static
{
$type = explode('charset', $this->getContentType())[0];
$this->withContentType(
rtrim($type,';') . ';charset=' . $charset
);
return $this;
}
/**
* @param $path
* @param bool $isChunk
* @param int $size
* @param int $offset
* @return OnDownloadInterface
* @throws Exception
*/
public function file($path, bool $isChunk = false, int $size = -1, int $offset = 0): OnDownloadInterface
{
$path = realpath($path);
if (!file_exists($path) || !is_readable($path)) {
throw new Exception('Cannot read file "' . $path . '", no permission');
}
return (new OnDownload())->path($path, $isChunk, $size, $offset);
}
}
+208
View File
@@ -0,0 +1,208 @@
<?php
namespace Http\Message;
use Http\Handler\Context;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
/**
*
*/
class ServerRequest extends Request implements ServerRequestInterface
{
const PARSE_BODY = 'with.parsed.body.callback';
/**
* @var mixed
*/
protected ?array $parsedBody = null;
/**
* @var array|null
*/
protected ?array $serverParams;
/**
* @var array|null
*/
protected ?array $queryParams;
/**
* @var array|null
*/
protected ?array $uploadedFiles;
protected \Swoole\Http\Request $serverTarget;
/**
* @param array $server
* @return static
*/
public function withServerParams(array $server): static
{
$this->serverParams = $server;
return $this;
}
/**
* @param \Swoole\Http\Request $server
* @return static
*/
public function withServerTarget(\Swoole\Http\Request $server): static
{
$this->serverTarget = $server;
return $this;
}
/**
* @param \Swoole\Http\Request $request
* @return static|ServerRequestInterface
* @throws \Exception
*/
public static function createServerRequest(\Swoole\Http\Request $request): static|ServerRequestInterface
{
$serverRequest = new ServerRequest();
$serverRequest->withData($request->getData());
$serverRequest->withServerParams($request->server);
$serverRequest->withServerTarget($request);
$serverRequest->withCookieParams($request->cookie);
$serverRequest->withUri(Uri::parseUri($request));
$serverRequest->withQueryParams($request->get ?? []);
$serverRequest->withUploadedFiles($request->files ?? []);
$serverRequest->withMethod($request->getMethod());
$serverRequest->withParsedBody($request->post);
return $serverRequest;
}
/**
* @return null|array
*/
public function getServerParams(): ?array
{
return $this->serverParams;
}
/**
* @return array|null
*/
public function getQueryParams(): ?array
{
return $this->queryParams;
}
/**
* @param array $query
* @return ServerRequestInterface
*/
public function withQueryParams(array $query): ServerRequestInterface
{
$this->queryParams = $query;
return $this;
}
/**
* @return array|null
*/
public function getUploadedFiles(): ?array
{
return $this->uploadedFiles;
}
/**
* @param array $uploadedFiles
* @return ServerRequestInterface
*/
public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
{
$this->uploadedFiles = $uploadedFiles;
return $this;
}
/**
* @return array|object|null
*/
public function getParsedBody(): object|array|null
{
if (empty($this->parsedBody)) {
$callback = Context::getContext(self::PARSE_BODY);
$this->parsedBody = $callback($this->getBody(), $this->serverTarget->post);
}
return $this->parsedBody;
}
/**
* @param array|object|null $data
* @return ServerRequestInterface
*/
public function withParsedBody($data): ServerRequestInterface
{
$functions = function (StreamInterface $stream) use ($data) {
$content = Parse::data($stream->getContents());
if (!empty($content)) {
return $content;
}
return $data;
};
Context::setContext(self::PARSE_BODY, $functions);
return $this;
}
/**
* @return array
*/
public function getAttributes(): array
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param string $name
* @param null $default
* @return mixed
*/
public function getAttribute($name, $default = null): mixed
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param string $name
* @param mixed $value
* @return ServerRequestInterface
*/
public function withAttribute($name, $value): ServerRequestInterface
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param string $name
* @return ServerRequestInterface
*/
public function withoutAttribute($name): ServerRequestInterface
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace Http\Message;
class StatusCode
{
const CODE_100 = 100;
const CODE_101 = 101;
const CODE_200 = 200;
const CODE_201 = 201;
const CODE_202 = 202;
const CODE_203 = 203;
const CODE_204 = 204;
const CODE_205 = 205;
const CODE_206 = 206;
const CODE_300 = 300;
const CODE_301 = 301;
const CODE_302 = 302;
const CODE_303 = 303;
const CODE_304 = 304;
const CODE_305 = 305;
const CODE_307 = 307;
const CODE_400 = 400;
const CODE_401 = 401;
const CODE_403 = 403;
const CODE_404 = 404;
const CODE_405 = 405;
const CODE_406 = 406;
const CODE_407 = 407;
const CODE_408 = 408;
const CODE_409 = 409;
const CODE_410 = 410;
const CODE_411 = 411;
const CODE_412 = 412;
const CODE_413 = 413;
const CODE_414 = 414;
const CODE_415 = 415;
const CODE_416 = 416;
const CODE_417 = 417;
const CODE_423 = 423;
const CODE_500 = 500;
const CODE_501 = 501;
const CODE_502 = 502;
const CODE_503 = 503;
const CODE_504 = 504;
const CODE_505 = 505;
const CODE_STATUS = [
self::CODE_100 => 'Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)',
self::CODE_101 => 'Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)',
self::CODE_200 => '(成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。',
self::CODE_201 => '(已创建) 请求成功并且服务器创建了新的资源。',
self::CODE_202 => '(已接受) 服务器已接受请求,但尚未处理。',
self::CODE_203 => '(非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。',
self::CODE_204 => '(无内容) 服务器成功处理了请求,但没有返回任何内容。',
self::CODE_205 => '(重置内容) 服务器成功处理了请求,但没有返回任何内容。',
self::CODE_206 => '(部分内容) 服务器成功处理了部分 GET 请求。',
self::CODE_300 => '(多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。',
self::CODE_301 => '(永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。',
self::CODE_302 => '(临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。',
self::CODE_303 => '(查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。',
self::CODE_304 => '(未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。',
self::CODE_305 => '(使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。',
self::CODE_307 => '(临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。',
self::CODE_400 => '(错误请求) 服务器不理解请求的语法。',
self::CODE_401 => '(未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。',
self::CODE_403 => '(禁止) 服务器拒绝请求。',
self::CODE_404 => '(未找到) 服务器找不到请求的网页。',
self::CODE_405 => '(方法禁用) 禁用请求中指定的方法。',
self::CODE_406 => '(不接受) 无法使用请求的内容特性响应请求的网页。',
self::CODE_407 => '(需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。',
self::CODE_408 => '(请求超时) 服务器等候请求时发生超时。',
self::CODE_409 => '(冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。',
self::CODE_410 => '(已删除) 如果请求的资源已永久删除,服务器就会返回此响应。',
self::CODE_411 => '(需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。',
self::CODE_412 => '(未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。',
self::CODE_413 => '(请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。',
self::CODE_414 => '(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。',
self::CODE_415 => '(不支持的媒体类型) 请求的格式不受请求页面的支持。',
self::CODE_416 => '(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。',
self::CODE_417 => '(未满足期望值) 服务器未满足"期望"请求标头字段的要求。',
self::CODE_423 => ' 锁定的错误。',
self::CODE_500 => '(服务器内部错误) 服务器遇到错误,无法完成请求。',
self::CODE_501 => '(尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。',
self::CODE_502 => '(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。',
self::CODE_503 => '(服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。',
self::CODE_504 => '(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。',
self::CODE_505 => '(HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。',
];
}
+237
View File
@@ -0,0 +1,237 @@
<?php
namespace Http\Message;
use JetBrains\PhpStorm\ArrayShape;
use Psr\Http\Message\StreamInterface;
class Stream implements StreamInterface
{
/**
* @var string|resource
*/
private mixed $content = '';
/**
* @var int
*/
private int $size = 0;
/**
* @param mixed $stream
*/
public function __construct(mixed $stream = '')
{
$this->content = $stream;
if (!is_resource($stream)) {
$this->size = strlen($stream);
} else {
$state = fstat($this->content);
if ($state) {
$this->size = $state['size'];
}
}
}
/**
* @return string
*/
public function __toString()
{
return $this->content;
}
/**
*
*/
public function close()
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return resource|null
*/
public function detach()
{
if (!is_resource($this->content)) {
throw new \BadMethodCallException('Not Accomplish Method.');
}
$steam = stream_context_create();
stream_copy_to_stream($this->content, $steam);
return $steam;
}
/**
* @return int
*/
public function getSize(): int
{
return $this->size;
}
/**
* @return bool|int
*/
public function tell(): bool|int
{
if (!is_resource($this->content)) {
throw new \BadMethodCallException('Not Accomplish Method.');
}
return ftell($this->content);
}
/**
* @return bool
*/
public function eof(): bool
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return bool
*/
public function isSeekable(): bool
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param int $offset
* @param int $whence
*/
public function seek($offset, $whence = SEEK_SET)
{
if (!is_resource($this->content)) {
throw new \BadMethodCallException('Not Accomplish Method.');
}
fseek($this->content, $offset, $whence);
}
/**
*
*/
public function rewind()
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return bool
*/
public function isWritable(): bool
{
if (!is_resource($this->content)) {
return true;
}
if (is_writable($this->content)) {
return true;
}
return false;
}
/**
* @param string $string
* @return int
*/
public function write($string): int
{
if (is_resource($this->content)) {
fwrite($this->content, $string);
$state = fstat($this->content);
if ($state) {
$this->size = $state['size'];
}
} else {
$this->content = $string;
$this->size = strlen($string);
}
return $this->size;
}
/**
* @return bool
*/
public function isReadable(): bool
{
if (!is_resource($this->content)) {
return true;
}
if (is_readable($this->content)) {
return true;
}
return false;
}
/**
* @param int $length
* @return false|string
*/
public function read($length): bool|string
{
if (is_resource($this->content)) {
return fread($this->content, $length);
} else {
return $this->content;
}
}
/**
* @return string|bool
*/
public function getContents(): string|bool
{
if (is_resource($this->content)) {
return stream_get_contents($this->content);
} else {
return $this->content;
}
}
/**
* @param null $key
* @return array
*/
#[ArrayShape([
"timed_out" => "bool",
"blocked" => "bool",
"eof" => "bool",
"unread_bytes" => "int",
"stream_type" => "string",
"wrapper_type" => "string",
"wrapper_data" => "mixed",
"mode" => "string",
"seekable" => "bool",
"uri" => "string",
"crypto" => "array",
"mediatype" => "string",
])]
public function getMetadata($key = null): array
{
if (is_resource($this->content)) {
return stream_get_meta_data($this->content);
}
throw new \BadMethodCallException('Not Accomplish Method.');
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace Http\Message;
use Exception;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
class Uploaded implements UploadedFileInterface
{
const ERROR = [
0 => "There is no error, the file uploaded with success",
1 => "The uploaded file exceeds the upload_max_filesize directive in php.ini",
2 => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
3 => "The uploaded file was only partially uploaded",
4 => "No file was uploaded",
6 => "Missing a temporary folder"
];
/**
* @var resource
*/
private mixed $stream;
/**
* @param string $tmp_name
* @param string $name
* @param string $type
* @param int $size
* @param int $error
*/
public function __construct(
public string $tmp_name,
public string $name,
public string $type,
public int $size,
public int $error
)
{
}
/**
* @return StreamInterface
* @throws Exception
*/
public function getStream(): StreamInterface
{
if ($this->stream instanceof Stream) {
return $this->stream;
}
$this->stream = new Stream(fopen($this->tmp_name, 'r+'));
return $this->stream;
}
/**
* @param string $targetPath
* @return StreamInterface
* @throws Exception
*/
public function moveTo($targetPath): StreamInterface
{
@move_uploaded_file($this->tmp_name, $targetPath);
if (!file_exists($targetPath)) {
throw new Exception('File save fail.');
}
if ($this->stream instanceof Stream) {
$this->stream->close();
$this->stream = null;
}
$this->tmp_name = $targetPath;
return $this->getStream();
}
/**
* @return int
*/
public function getSize(): int
{
return $this->size;
}
/**
* @return int
*/
public function getError(): int
{
return $this->error;
}
/**
* @return string
*/
public function getClientFilename(): string
{
return $this->name;
}
/**
* @return string
*/
public function getClientMediaType(): string
{
return $this->type;
}
}
+256
View File
@@ -0,0 +1,256 @@
<?php
namespace Http\Message;
use Psr\Http\Message\UriInterface;
use Swoole\Http\Request;
class Uri implements UriInterface
{
protected string $scheme = '';
protected string $host = '';
protected string $path = '';
protected string $fragment = '';
protected int $port = 80;
protected string $queryString = '';
/**
* @return string
*/
public function getScheme(): string
{
return $this->scheme;
}
/**
* @return string
*/
public function getAuthority(): string
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return string
*/
public function getUserInfo(): string
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return string
*/
public function getHost(): string
{
return $this->host;
}
/**
* @return int|null
*/
public function getPort(): ?int
{
return $this->port;
}
/**
* @return string
*/
public function getPath(): string
{
return $this->path;
}
/**
* @return string
*/
public function getQuery(): string
{
return $this->queryString;
}
/**
* @return string
*/
public function getFragment(): string
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param string $scheme
* @return $this|Uri
*/
public function withScheme($scheme): UriInterface
{
$this->scheme = $scheme;
return $this;
}
/**
* @param string $user
* @param null $password
* @return Uri
*/
public function withUserInfo($user, $password = null): UriInterface
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @param string $host
* @return $this|Uri
*/
public function withHost($host): UriInterface
{
$this->host = $host;
return $this;
}
/**
* @param int|null $port
* @return $this|Uri
*/
public function withPort($port): UriInterface
{
$this->port = $port;
return $this;
}
/**
* @param string $path
* @return $this|Uri
*/
public function withPath($path): UriInterface
{
$this->path = $path;
return $this;
}
/**
* @param string $query
* @return $this|Uri
*/
public function withQuery($query): UriInterface
{
$this->queryString = $query;
return $this;
}
/**
* @param string $fragment
* @return Uri
*/
public function withFragment($fragment): UriInterface
{
throw new \BadMethodCallException('Not Accomplish Method.');
}
/**
* @return string
*/
public function __toString(): string
{
$domain = sprintf('%s://%s', $this->scheme, $this->host);
if (!in_array($this->port, [80, 443])) {
$domain .= ':' . $this->port;
}
if (empty($this->query) && empty($this->fragment)) {
return $domain . $this->path;
}
return sprintf('%s?%s#%s', $domain . $this->path,
$this->queryString, $this->fragment);
}
/**
* @return int
*/
public function getDefaultPort(): int
{
return $this->scheme == 'https' ? 443 : 80;
}
/**
* @param Request $request
* @return UriInterface
*/
public static function parseUri(Request $request): UriInterface
{
$server = $request->server;
$header = $request->header;
$uri = new static();
$uri = $uri->withScheme(!empty($server['https']) && $server['https'] !== 'off' ? 'https' : 'http');
if (isset($request->header['x-forwarded-proto'])) {
$uri->withScheme($request->header['x-forwarded-proto'])->withPort(443);
}
$hasPort = false;
if (isset($server['http_host'])) {
$hostHeaderParts = explode(':', $server['http_host']);
$uri = $uri->withHost($hostHeaderParts[0]);
if (isset($hostHeaderParts[1])) {
$hasPort = true;
$uri = $uri->withPort($hostHeaderParts[1]);
}
} elseif (isset($server['server_name'])) {
$uri = $uri->withHost($server['server_name']);
} elseif (isset($server['server_addr'])) {
$uri = $uri->withHost($server['server_addr']);
} elseif (isset($header['host'])) {
$hasPort = true;
if (strpos($header['host'], ':')) {
[$host, $port] = explode(':', $header['host'], 2);
if ($port != $uri->getDefaultPort()) {
$uri = $uri->withPort($port);
}
} else {
$host = $header['host'];
}
$uri = $uri->withHost($host);
}
if (!$hasPort && isset($server['server_port'])) {
$uri = $uri->withPort($server['server_port']);
}
$hasQuery = false;
if (isset($server['request_uri'])) {
$requestUriParts = explode('?', $server['request_uri']);
$uri = $uri->withPath($requestUriParts[0]);
if (isset($requestUriParts[1])) {
$hasQuery = true;
$uri = $uri->withQuery($requestUriParts[1]);
}
}
if (!$hasQuery && isset($server['query_string'])) {
$uri = $uri->withQuery($server['query_string']);
}
return $uri;
}
}