Skip to content

Commit ac92a66

Browse files
author
=
committed
First commit
1 parent ae686ae commit ac92a66

File tree

13 files changed

+1943
-1
lines changed

13 files changed

+1943
-1
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/.idea/
2+
/.vscode/
3+
/.vs/
4+
/vendor/
5+
/composer.lock

README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1-
# HTTP
1+
# InitPHP HTTP
2+
3+
This library provides HTTP Message and HTTP Factory solution following PSR-7 and PSR-17 standards. It also includes an Emitter class for the PSR-7.
4+
5+
[![Latest Stable Version](http://poser.pugx.org/initphp/http/v)](https://packagist.org/packages/initphp/http) [![Total Downloads](http://poser.pugx.org/initphp/http/downloads)](https://packagist.org/packages/initphp/http) [![Latest Unstable Version](http://poser.pugx.org/initphp/http/v/unstable)](https://packagist.org/packages/initphp/http) [![License](http://poser.pugx.org/initphp/http/license)](https://packagist.org/packages/initphp/http) [![PHP Version Require](http://poser.pugx.org/initphp/http/require/php)](https://packagist.org/packages/initphp/http)
6+
7+
## Requirements
8+
9+
- PHP 7.4 or higher
10+
- PSR-7 HTTP Message Interfaces
11+
- PSR-17 HTTP Factories Interfaces
12+
13+
## Installation
14+
15+
```
16+
composer require initphp/http
17+
```
18+
19+
## Usage
20+
21+
It adheres to the PSR-7 and PSR-17 standards and strictly implements these interfaces to a large extent.
22+
23+
### Emitter Usage
24+
25+
```php
26+
use \InitPHP\HTTP\{Response, Emitter, Stream};
27+
28+
29+
$response = new Response(200, [], new Stream('Hello World', null), '1.1');
30+
31+
$emitter = new Emitter;
32+
$emitter->emit($response);
33+
```
34+
35+
#### A Small Difference For PSR-7 Stream
36+
37+
If you are working with small content; The PSR-7 Stream interface may be cumbersome for you. This is because the PSR-7 stream interface writes the content "`php://temp`" or "`php://memory`". By default this library will also overwrite `php://temp` with your content. To change this behavior, this must be declared as the second parameter to the constructor method when creating the Stream object.
38+
39+
```php
40+
use \InitPHP\HTTP\Stream;
41+
42+
/**
43+
* This content is kept in memory as a variable.
44+
*/
45+
$variableStream = new Stream('String Content', null);
46+
47+
/**
48+
* Content; "php://memory" is overwritten.
49+
*/
50+
$memoryStream = new Stream('Content', 'php://memory');
51+
52+
/**
53+
* Content; "php://temp" is overwritten.
54+
*/
55+
$tempStream = new Stream('Content', 'php://temp');
56+
// or new Stream('Content');
57+
```
58+
59+
## Credits
60+
61+
- [Muhammet ŞAFAK](https://www.muhammetsafak.com.tr) <<[email protected]>>
62+
63+
## License
64+
65+
Copyright &copy; 2022 [MIT License](./LICENSE)

composer.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "initphp/http",
3+
"description": "InitPHP PSR-7 HTTP Message Library",
4+
"type": "library",
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-4": {
8+
"InitPHP\\HTTP\\": "src/"
9+
}
10+
},
11+
"authors": [
12+
{
13+
"name": "Muhammet ŞAFAK",
14+
"email": "[email protected]",
15+
"role": "Developer",
16+
"homepage": "https://www.muhammetsafak.com.tr"
17+
}
18+
],
19+
"minimum-stability": "stable",
20+
"require": {
21+
"php": ">=7.4",
22+
"psr/http-message": "^1.0",
23+
"psr/http-factory": "^1.0"
24+
}
25+
}

src/Emitter.php

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
/**
3+
* Emitter.php
4+
*
5+
* This file is part of InitPHP.
6+
*
7+
* @author Muhammet ŞAFAK <[email protected]>
8+
* @copyright Copyright © 2022 InitPHP
9+
* @license http://initphp.github.com/license.txt MIT
10+
* @version 1.0
11+
* @link https://www.muhammetsafak.com.tr
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
namespace InitPHP\HTTP;
17+
18+
use \Psr\Http\Message\{ResponseInterface, StreamInterface};
19+
20+
use function assert;
21+
use function is_string;
22+
use function ucfirst;
23+
use function header;
24+
use function sprintf;
25+
use function headers_sent;
26+
use function is_int;
27+
use function ob_get_level;
28+
use function ob_get_length;
29+
use function flush;
30+
use function strlen;
31+
use function preg_match;
32+
33+
class Emitter
34+
{
35+
36+
protected ?int $bufferLength = null;
37+
38+
public function emit(ResponseInterface $response, ?int $bufferLength = null)
39+
{
40+
if($bufferLength !== null && $bufferLength > 0){
41+
$this->bufferLength = $bufferLength;
42+
}
43+
$this->assertNoPreviousOutput();
44+
$this->emitHeaders($response);
45+
$this->emitStatusLine($response);
46+
$this->emitBody($response);
47+
}
48+
49+
private function emitBody(ResponseInterface $response): void
50+
{
51+
if($this->bufferLength === null){
52+
echo $response->getBody();
53+
return;
54+
}
55+
flush();
56+
$stream = $response->getBody();
57+
$range = $this->parseHeaderContentRange($response->getHeaderLine('content-range'));
58+
if(isset($range['unit']) && $range['unit'] === 'bytes'){
59+
$this->emitBodyRange($stream, $range['first'], $range['last']);
60+
return;
61+
}
62+
if($stream->isSeekable()){
63+
$stream->rewind();
64+
}
65+
while(!$stream->eof()){
66+
echo $stream->read($this->bufferLength);
67+
}
68+
}
69+
70+
private function emitBodyRange(StreamInterface $body, int $first, int $last): void
71+
{
72+
$length = $last - ($first + 1);
73+
if($body->isSeekable()){
74+
$body->seek($first);
75+
}
76+
while($length >= $this->bufferLength && !$body->eof()){
77+
$content = $body->read($this->bufferLength);
78+
$length -= strlen($content);
79+
echo $content;
80+
}
81+
if($length > 0 && !$body->eof()){
82+
echo $body->read($length);
83+
}
84+
}
85+
86+
private function assertNoPreviousOutput(): void
87+
{
88+
$filename = null;
89+
$line = null;
90+
if(headers_sent($filename, $line)){
91+
assert(is_string($filename) && is_int($line));
92+
throw new \RuntimeException(\sprintf('Unable to emit response; headers already sent in %s:%d', $filename, $line));
93+
}
94+
if(ob_get_level() > 0 && ob_get_length() > 0){
95+
throw new \RuntimeException('Output has been emitted previously; cannot emit response');
96+
}
97+
}
98+
99+
private function emitStatusLine(ResponseInterface $response): void
100+
{
101+
$reasonPhrase = $response->getReasonPhrase();
102+
$statusCode = $response->getStatusCode();
103+
header(sprintf(
104+
'HTTP/%s %d%s',
105+
$response->getProtocolVersion(),
106+
$statusCode, ($reasonPhrase ? ' ' . $reasonPhrase : '')
107+
), true, $statusCode);
108+
}
109+
110+
private function emitHeaders(ResponseInterface $response): void
111+
{
112+
$statusCode = $response->getStatusCode();
113+
114+
foreach ($response->getHeaders() as $header => $values){
115+
assert(is_string($header));
116+
$name = ucfirst($header);
117+
$first = ($name !== 'Set-Cookie');
118+
foreach ($values as $value){
119+
header(sprintf('%s: %s', $name, $value), $first, $statusCode);
120+
$first = false;
121+
}
122+
}
123+
}
124+
125+
private function parseHeaderContentRange(string $header): ?array
126+
{
127+
if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
128+
return [
129+
'unit' => $matches['unit'],
130+
'first' => (int)$matches['first'],
131+
'last' => (int)$matches['last'],
132+
'length' => ($matches['length'] === '*') ? '*' : (int)$matches['length'],
133+
];
134+
}
135+
return null;
136+
}
137+
138+
}

src/Factory.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
/**
3+
* Factory.php
4+
*
5+
* This file is part of InitPHP.
6+
*
7+
* @author Muhammet ŞAFAK <[email protected]>
8+
* @copyright Copyright © 2022 InitPHP
9+
* @license http://initphp.github.com/license.txt MIT
10+
* @version 1.0
11+
* @link https://www.muhammetsafak.com.tr
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
namespace InitPHP\HTTP;
17+
18+
use \Psr\Http\Message\{RequestFactoryInterface,
19+
RequestInterface,
20+
ResponseInterface,
21+
ServerRequestInterface,
22+
UploadedFileInterface,
23+
UriFactoryInterface,
24+
UploadedFileFactoryInterface,
25+
StreamFactoryInterface,
26+
ServerRequestFactoryInterface,
27+
ResponseFactoryInterface,
28+
StreamInterface,
29+
UriInterface};
30+
31+
use function in_array;
32+
use function fopen;
33+
use function error_get_last;
34+
35+
class Factory implements RequestFactoryInterface, UriFactoryInterface, UploadedFileFactoryInterface, StreamFactoryInterface, ServerRequestFactoryInterface, ResponseFactoryInterface
36+
{
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
public function createRequest(string $method, $uri): RequestInterface
42+
{
43+
return new Request($method, $uri);
44+
}
45+
46+
/**
47+
* @inheritDoc
48+
*/
49+
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
50+
{
51+
return new Response($code, [], null, '1.1', ($reasonPhrase === '' ? null : $reasonPhrase));
52+
}
53+
54+
/**
55+
* @inheritDoc
56+
*/
57+
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
58+
{
59+
return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
60+
}
61+
62+
/**
63+
* @inheritDoc
64+
*/
65+
public function createStream(string $content = ''): StreamInterface
66+
{
67+
return new Stream($content);
68+
}
69+
70+
/**
71+
* @inheritDoc
72+
*/
73+
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
74+
{
75+
if($filename === ''){
76+
throw new \RuntimeException('Path cannot be empty');
77+
}
78+
if($mode === '' || in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true) === FALSE){
79+
throw new \InvalidArgumentException(sprintf('The mode "%s" is invalid.', $mode));
80+
}
81+
82+
if(FALSE === $resource = @fopen($filename, $mode)){
83+
throw new \RuntimeException(sprintf('The file "%s" cannot be opened: %s', $filename, error_get_last()['message'] ?? ''));
84+
}
85+
return new Stream($resource);
86+
}
87+
88+
/**
89+
* @inheritDoc
90+
*/
91+
public function createStreamFromResource($resource): StreamInterface
92+
{
93+
if($resource instanceof StreamInterface){
94+
return $resource;
95+
}
96+
return new Stream($resource);
97+
}
98+
99+
/**
100+
* @inheritDoc
101+
*/
102+
public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface
103+
{
104+
if($size === null){
105+
$size = $stream->getSize();
106+
}
107+
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
108+
}
109+
110+
/**
111+
* @inheritDoc
112+
*/
113+
public function createUri(string $uri = ''): UriInterface
114+
{
115+
return new Uri($uri);
116+
}
117+
118+
}

0 commit comments

Comments
 (0)