pax_global_header 0000666 0000000 0000000 00000000064 13263421677 0014525 g ustar 00root root 0000000 0000000 52 comment=f8bcdab2dc0ecd94f35ff9657a263028b96f0c46
http-0.8.3/ 0000775 0000000 0000000 00000000000 13263421677 0012514 5 ustar 00root root 0000000 0000000 http-0.8.3/.gitignore 0000664 0000000 0000000 00000000025 13263421677 0014501 0 ustar 00root root 0000000 0000000 composer.lock
vendor
http-0.8.3/.travis.yml 0000664 0000000 0000000 00000001270 13263421677 0014625 0 ustar 00root root 0000000 0000000 language: php
php:
# - 5.3 # requires old distro, see below
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm # ignore errors, see below
# lock distro so new future defaults will not break the build
dist: trusty
# also test lowest dependencies on PHP 7
matrix:
include:
- php: 5.3
dist: precise
- php: 7.0
env:
- DEPENDENCIES=lowest
allow_failures:
- php: hhvm
sudo: false
install:
- composer install --no-interaction
- if [ "$DEPENDENCIES" = "lowest" ]; then composer update --prefer-lowest -n; fi
script:
- ./vendor/bin/phpunit --coverage-text
- if [ "$DEPENDENCIES" = "lowest" ]; then php -n tests/benchmark-middleware-runner.php; fi
http-0.8.3/CHANGELOG.md 0000664 0000000 0000000 00000043577 13263421677 0014345 0 ustar 00root root 0000000 0000000 # Changelog
## 0.8.3 (2018-04-11)
* Feature: Do not pause connection stream to detect closed connections immediately.
(#315 by @clue)
* Feature: Keep incoming `Transfer-Encoding: chunked` request header.
(#316 by @clue)
* Feature: Reject invalid requests that contain both `Content-Length` and `Transfer-Encoding` request headers.
(#318 by @clue)
* Minor internal refactoring to simplify connection close logic after sending response.
(#317 by @clue)
## 0.8.2 (2018-04-06)
* Fix: Do not pass `$next` handler to final request handler.
(#308 by @clue)
* Fix: Fix awaiting queued handlers when cancelling a queued handler.
(#313 by @clue)
* Fix: Fix Server to skip `SERVER_ADDR` params for Unix domain sockets (UDS).
(#307 by @clue)
* Documentation for PSR-15 middleware and minor documentation improvements.
(#314 by @clue and #297, #298 and #310 by @seregazhuk)
* Minor code improvements and micro optimizations.
(#301 by @seregazhuk and #305 by @kalessil)
## 0.8.1 (2018-01-05)
* Major request handler performance improvement. Benchmarks suggest number of
requests/s improved by more than 50% for common `GET` requests!
We now avoid queuing, buffering and wrapping incoming requests in promises
when we're below limits and instead can directly process common requests.
(#291, #292, #293, #294 and #296 by @clue)
* Fix: Fix concurrent invoking next middleware request handlers
(#293 by @clue)
* Small code improvements
(#286 by @seregazhuk)
* Improve test suite to be less fragile when using `ext-event` and
fix test suite forward compatibility with upcoming EventLoop releases
(#288 and #290 by @clue)
## 0.8.0 (2017-12-12)
* Feature / BC break: Add new `Server` facade that buffers and parses incoming
HTTP requests. This provides full PSR-7 compatibility, including support for
form submissions with POST fields and file uploads.
The old `Server` has been renamed to `StreamingServer` for advanced usage
and is used internally.
(#266, #271, #281, #282, #283 and #284 by @WyriHaximus and @clue)
```php
// old: handle incomplete/streaming requests
$server = new Server($handler);
// new: handle complete, buffered and parsed requests
// new: full PSR-7 support, including POST fields and file uploads
$server = new Server($handler);
// new: handle incomplete/streaming requests
$server = new StreamingServer($handler);
```
> While this is technically a small BC break, this should in fact not break
most consuming code. If you rely on the old request streaming, you can
explicitly use the advanced `StreamingServer` to restore old behavior.
* Feature: Add support for middleware request handler arrays
(#215, #228, #229, #236, #237, #238, #246, #247, #277, #279 and #285 by @WyriHaximus, @clue and @jsor)
```php
// new: middleware request handler arrays
$server = new Server(array(
function (ServerRequestInterface $request, callable $next) {
$request = $request->withHeader('Processed', time());
return $next($request);
},
function (ServerRequestInterface $request) {
return new Response();
}
));
```
* Feature: Add support for limiting how many next request handlers can be
executed concurrently (`LimitConcurrentRequestsMiddleware`)
(#272 by @clue and @WyriHaximus)
```php
// new: explicitly limit concurrency
$server = new Server(array(
new LimitConcurrentRequestsMiddleware(10),
$handler
));
```
* Feature: Add support for buffering the incoming request body
(`RequestBodyBufferMiddleware`).
This feature mimics PHP's default behavior and respects its `post_max_size`
ini setting by default and allows explicit configuration.
(#216, #224, #263, #276 and #278 by @WyriHaximus and #235 by @andig)
```php
// new: buffer up to 10 requests with 8 MiB each
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(10),
new RequestBodyBufferMiddleware('8M'),
$handler
));
```
* Feature: Add support for parsing form submissions with POST fields and file
uploads (`RequestBodyParserMiddleware`).
This feature mimics PHP's default behavior and respects its ini settings and
`MAX_FILE_SIZE` POST fields by default and allows explicit configuration.
(#220, #226, #252, #261, #264, #265, #267, #268, #274 by @WyriHaximus and @clue)
```php
// new: buffer up to 10 requests with 8 MiB each
// and limit to 4 uploads with 2 MiB each
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(10),
new RequestBodyBufferMiddleware('8M'),
new RequestBodyParserMiddleware('2M', 4)
$handler
));
```
* Feature: Update Socket to work around sending secure HTTPS responses with PHP < 7.1.4
(#244 by @clue)
* Feature: Support sending same response header multiple times (e.g. `Set-Cookie`)
(#248 by @clue)
* Feature: Raise maximum request header size to 8k to match common implementations
(#253 by @clue)
* Improve test suite by adding forward compatibility with PHPUnit 6, test
against PHP 7.1 and PHP 7.2 and refactor and remove risky and duplicate tests.
(#243, #269 and #270 by @carusogabriel and #249 by @clue)
* Minor code refactoring to move internal classes to `React\Http\Io` namespace
and clean up minor code and documentation issues
(#251 by @clue, #227 by @kalessil, #240 by @christoph-kluge, #230 by @jsor and #280 by @andig)
## 0.7.4 (2017-08-16)
* Improvement: Target evenement 3.0 a long side 2.0 and 1.0
(#212 by @WyriHaximus)
## 0.7.3 (2017-08-14)
* Feature: Support `Throwable` when setting previous exception from server callback
(#155 by @jsor)
* Fix: Fixed URI parsing for origin-form requests that contain scheme separator
such as `/path?param=http://example.com`.
(#209 by @aaronbonneau)
* Improve test suite by locking Travis distro so new defaults will not break the build
(#211 by @clue)
## 0.7.2 (2017-07-04)
* Fix: Stricter check for invalid request-line in HTTP requests
(#206 by @clue)
* Refactor to use HTTP response reason phrases from response object
(#205 by @clue)
## 0.7.1 (2017-06-17)
* Fix: Fix parsing CONNECT request without `Host` header
(#201 by @clue)
* Internal preparation for future PSR-7 `UploadedFileInterface`
(#199 by @WyriHaximus)
## 0.7.0 (2017-05-29)
* Feature / BC break: Use PSR-7 (http-message) standard and
`Request-In-Response-Out`-style request handler callback.
Pass standard PSR-7 `ServerRequestInterface` and expect any standard
PSR-7 `ResponseInterface` in return for the request handler callback.
(#146 and #152 and #170 by @legionth)
```php
// old
$app = function (Request $request, Response $response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Hello world!\n");
};
// new
$app = function (ServerRequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Hello world!\n"
);
};
```
A `Content-Length` header will automatically be included if the size can be
determined from the response body.
(#164 by @maciejmrozinski)
The request handler callback will automatically make sure that responses to
HEAD requests and certain status codes, such as `204` (No Content), never
contain a response body.
(#156 by @clue)
The intermediary `100 Continue` response will automatically be sent if
demanded by a HTTP/1.1 client.
(#144 by @legionth)
The request handler callback can now return a standard `Promise` if
processing the request needs some time, such as when querying a database.
Similarly, the request handler may return a streaming response if the
response body comes from a `ReadableStreamInterface` or its size is
unknown in advance.
```php
// old
$app = function (Request $request, Response $response) use ($db) {
$db->query()->then(function ($result) use ($response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end($result);
});
};
// new
$app = function (ServerRequestInterface $request) use ($db) {
return $db->query()->then(function ($result) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
$result
);
});
};
```
Pending promies and response streams will automatically be canceled once the
client connection closes.
(#187 and #188 by @clue)
The `ServerRequestInterface` contains the full effective request URI,
server-side parameters, query parameters and parsed cookies values as
defined in PSR-7.
(#167 by @clue and #174, #175 and #180 by @legionth)
```php
$app = function (ServerRequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
$request->getUri()->getScheme()
);
};
```
Advanced: Support duplex stream response for `Upgrade` requests such as
`Upgrade: WebSocket` or custom protocols and `CONNECT` requests
(#189 and #190 by @clue)
> Note that the request body will currently not be buffered and parsed by
default, which depending on your particilar use-case, may limit
interoperability with the PSR-7 (http-message) ecosystem.
The provided streaming request body interfaces allow you to perform
buffering and parsing as needed in the request handler callback.
See also the README and examples for more details.
* Feature / BC break: Replace `request` listener with callback function and
use `listen()` method to support multiple listening sockets
(#97 by @legionth and #193 by @clue)
```php
// old
$server = new Server($socket);
$server->on('request', $app);
// new
$server = new Server($app);
$server->listen($socket);
```
* Feature: Support the more advanced HTTP requests, such as
`OPTIONS * HTTP/1.1` (`OPTIONS` method in asterisk-form),
`GET http://example.com/path HTTP/1.1` (plain proxy requests in absolute-form),
`CONNECT example.com:443 HTTP/1.1` (`CONNECT` proxy requests in authority-form)
and sanitize `Host` header value across all requests.
(#157, #158, #161, #165, #169 and #173 by @clue)
* Feature: Forward compatibility with Socket v1.0, v0.8, v0.7 and v0.6 and
forward compatibility with Stream v1.0 and v0.7
(#154, #163, #183, #184 and #191 by @clue)
* Feature: Simplify examples to ease getting started and
add benchmarking example
(#151 and #162 by @clue)
* Improve test suite by adding tests for case insensitive chunked transfer
encoding and ignoring HHVM test failures until Travis tests work again.
(#150 by @legionth and #185 by @clue)
## 0.6.0 (2017-03-09)
* Feature / BC break: The `Request` and `Response` objects now follow strict
stream semantics and their respective methods and events.
(#116, #129, #133, #135, #136, #137, #138, #140, #141 by @legionth
and #122, #123, #130, #131, #132, #142 by @clue)
This implies that the `Server` now supports proper detection of the request
message body stream, such as supporting decoding chunked transfer encoding,
delimiting requests with an explicit `Content-Length` header
and those with an empty request message body.
These streaming semantics are compatible with previous Stream v0.5, future
compatible with v0.5 and upcoming v0.6 versions and can be used like this:
```php
$http->on('request', function (Request $request, Response $response) {
$contentLength = 0;
$request->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});
$request->on('end', function () use ($response, &$contentLength){
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("The length of the submitted request body is: " . $contentLength);
});
// an error occured
// e.g. on invalid chunked encoded data or an unexpected 'end' event
$request->on('error', function (\Exception $exception) use ($response, &$contentLength) {
$response->writeHead(400, array('Content-Type' => 'text/plain'));
$response->end("An error occured while reading at length: " . $contentLength);
});
});
```
Similarly, the `Request` and `Response` now strictly follow the
`close()` method and `close` event semantics.
Closing the `Request` does not interrupt the underlying TCP/IP in
order to allow still sending back a valid response message.
Closing the `Response` does terminate the underlying TCP/IP
connection in order to clean up resources.
You should make sure to always attach a `request` event listener
like above. The `Server` will not respond to an incoming HTTP
request otherwise and keep the TCP/IP connection pending until the
other side chooses to close the connection.
* Feature: Support `HTTP/1.1` and `HTTP/1.0` for `Request` and `Response`.
(#124, #125, #126, #127, #128 by @clue and #139 by @legionth)
The outgoing `Response` will automatically use the same HTTP version as the
incoming `Request` message and will only apply `HTTP/1.1` semantics if
applicable. This includes that the `Response` will automatically attach a
`Date` and `Connection: close` header if applicable.
This implies that the `Server` now automatically responds with HTTP error
messages for invalid requests (status 400) and those exceeding internal
request header limits (status 431).
## 0.5.0 (2017-02-16)
* Feature / BC break: Change `Request` methods to be in line with PSR-7
(#117 by @clue)
* Rename `getQuery()` to `getQueryParams()`
* Rename `getHttpVersion()` to `getProtocolVersion()`
* Change `getHeaders()` to always return an array of string values
for each header
* Feature / BC break: Update Socket component to v0.5 and
add secure HTTPS server support
(#90 and #119 by @clue)
```php
// old plaintext HTTP server
$socket = new React\Socket\Server($loop);
$socket->listen(8080, '127.0.0.1');
$http = new React\Http\Server($socket);
// new plaintext HTTP server
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$http = new React\Http\Server($socket);
// new secure HTTPS server
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$socket = new React\Socket\SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/localhost.pem'
));
$http = new React\Http\Server($socket);
```
* BC break: Mark internal APIs as internal or private and
remove unneeded `ServerInterface`
(#118 by @clue, #95 by @legionth)
## 0.4.4 (2017-02-13)
* Feature: Add request header accessors (à la PSR-7)
(#103 by @clue)
```php
// get value of host header
$host = $request->getHeaderLine('Host');
// get list of all cookie headers
$cookies = $request->getHeader('Cookie');
```
* Feature: Forward `pause()` and `resume()` from `Request` to underlying connection
(#110 by @clue)
```php
// support back-pressure when piping request into slower destination
$request->pipe($dest);
// manually pause/resume request
$request->pause();
$request->resume();
```
* Fix: Fix `100-continue` to be handled case-insensitive and ignore it for HTTP/1.0.
Similarly, outgoing response headers are now handled case-insensitive, e.g
we no longer apply chunked transfer encoding with mixed-case `Content-Length`.
(#107 by @clue)
```php
// now handled case-insensitive
$request->expectsContinue();
// now works just like properly-cased header
$response->writeHead($status, array('content-length' => 0));
```
* Fix: Do not emit empty `data` events and ignore empty writes in order to
not mess up chunked transfer encoding
(#108 and #112 by @clue)
* Lock and test minimum required dependency versions and support PHPUnit v5
(#113, #115 and #114 by @andig)
## 0.4.3 (2017-02-10)
* Fix: Do not take start of body into account when checking maximum header size
(#88 by @nopolabs)
* Fix: Remove `data` listener if `HeaderParser` emits an error
(#83 by @nick4fake)
* First class support for PHP 5.3 through PHP 7 and HHVM
(#101 and #102 by @clue, #66 by @WyriHaximus)
* Improve test suite by adding PHPUnit to require-dev,
improving forward compatibility with newer PHPUnit versions
and replacing unneeded test stubs
(#92 and #93 by @nopolabs, #100 by @legionth)
## 0.4.2 (2016-11-09)
* Remove all listeners after emitting error in RequestHeaderParser #68 @WyriHaximus
* Catch Guzzle parse request errors #65 @WyriHaximus
* Remove branch-alias definition as per reactphp/react#343 #58 @WyriHaximus
* Add functional example to ease getting started #64 by @clue
* Naming, immutable array manipulation #37 @cboden
## 0.4.1 (2015-05-21)
* Replaced guzzle/parser with guzzlehttp/psr7 by @cboden
* FIX Continue Header by @iannsp
* Missing type hint by @marenzo
## 0.4.0 (2014-02-02)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: Update to React/Promise 2.0
* BC break: Update to Evenement 2.0
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
* Bump React dependencies to v0.4
## 0.3.0 (2013-04-14)
* Bump React dependencies to v0.3
## 0.2.6 (2012-12-26)
* Bug fix: Emit end event when Response closes (@beaucollins)
## 0.2.3 (2012-11-14)
* Bug fix: Forward drain events from HTTP response (@cs278)
* Dependency: Updated guzzle deps to `3.0.*`
## 0.2.2 (2012-10-28)
* Version bump
## 0.2.1 (2012-10-14)
* Feature: Support HTTP 1.1 continue
## 0.2.0 (2012-09-10)
* Bump React dependencies to v0.2
## 0.1.1 (2012-07-12)
* Version bump
## 0.1.0 (2012-07-11)
* First tagged release
http-0.8.3/LICENSE 0000664 0000000 0000000 00000002055 13263421677 0013523 0 ustar 00root root 0000000 0000000 Copyright (c) 2012 Igor Wiedler, Chris Boden
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
http-0.8.3/README.md 0000664 0000000 0000000 00000135001 13263421677 0013773 0 ustar 00root root 0000000 0000000 # Http
[](https://travis-ci.org/reactphp/http)
Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](https://reactphp.org/).
**Table of Contents**
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [Server](#server)
* [StreamingServer](#streamingserver)
* [Request](#request)
* [Request parameters](#request-parameters)
* [Query parameters](#query-parameters)
* [Streaming request](#streaming-request)
* [Request method](#request-method)
* [Cookie parameters](#cookie-parameters)
* [Response](#response)
* [Deferred response](#deferred-response)
* [Streaming response](#streaming-response)
* [Response length](#response-length)
* [Invalid response](#invalid-response)
* [Default response headers](#default-response-headers)
* [Middleware](#middleware)
* [LimitConcurrentRequestsMiddleware](#limitconcurrentrequestsmiddleware)
* [RequestBodyBufferMiddleware](#requestbodybuffermiddleware)
* [RequestBodyParserMiddleware](#requestbodyparsermiddleware)
* [Third-Party Middleware](#third-party-middleware)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Quickstart example
This is an HTTP server which responds with `Hello World!` to every request.
```php
$loop = React\EventLoop\Factory::create();
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello World!\n"
);
});
$socket = new React\Socket\Server(8080, $loop);
$server->listen($socket);
$loop->run();
```
See also the [examples](examples).
## Usage
### Server
The `Server` class is responsible for handling incoming connections and then
processing each incoming HTTP request.
It buffers and parses the complete incoming HTTP request in memory. Once the
complete request has been received, it will invoke the request handler.
For each request, it executes the callback function passed to the
constructor with the respective [request](#request) object and expects
a respective [response](#response) object in return.
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello World!\n"
);
});
```
For most users a server that buffers and parses a requests before handling it over as a
PSR-7 request is what they want. The `Server` facade takes care of that, and takes the more
advanced configuration out of hand. Under the hood it uses [StreamingServer](#streamingserver)
with the the three stock middleware using default settings from `php.ini`.
The [LimitConcurrentRequestsMiddleware](#limitconcurrentrequestsmiddleware) requires a limit,
as such the `Server` facade uses the `memory_limit` and `post_max_size` ini settings to
calculate a sensible limit. It assumes a maximum of a quarter of the `memory_limit` for
buffering and the other three quarter for parsing and handling the requests. The limit is
division of half of `memory_limit` by `memory_limit` rounded up.
> Note that any errors emitted by the wrapped `StreamingServer` are forwarded by `Server`.
### StreamingServer
The advanced `StreamingServer` class is responsible for handling incoming connections and then
processing each incoming HTTP request.
Unlike the [`Server`](#server) class, it does not buffer and parse the incoming
HTTP request body by default. This means that the request handler will be
invoked with a streaming request body.
For each request, it executes the callback function passed to the
constructor with the respective [request](#request) object and expects
a respective [response](#response) object in return.
```php
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello World!\n"
);
});
```
In order to process any connections, the server needs to be attached to an
instance of `React\Socket\ServerInterface` which emits underlying streaming
connections in order to then parse incoming data as HTTP.
You can attach this to a
[`React\Socket\Server`](https://github.com/reactphp/socket#server)
in order to start a plaintext HTTP server like this:
```php
$server = new StreamingServer($handler);
$socket = new React\Socket\Server(8080, $loop);
$server->listen($socket);
```
See also the `listen()` method and the [first example](examples) for more details.
Similarly, you can also attach this to a
[`React\Socket\SecureServer`](https://github.com/reactphp/socket#secureserver)
in order to start a secure HTTPS server like this:
```php
$server = new StreamingServer($handler);
$socket = new React\Socket\Server(8080, $loop);
$socket = new React\Socket\SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/localhost.pem'
));
$server->listen($socket);
```
See also [example #11](examples) for more details.
When HTTP/1.1 clients want to send a bigger request body, they MAY send only
the request headers with an additional `Expect: 100-continue` header and
wait before sending the actual (large) message body.
In this case the server will automatically send an intermediary
`HTTP/1.1 100 Continue` response to the client.
This ensures you will receive the request body without a delay as expected.
The [Response](#response) still needs to be created as described in the
examples above.
See also [request](#request) and [response](#response)
for more details (e.g. the request data body).
The `StreamingServer` supports both HTTP/1.1 and HTTP/1.0 request messages.
If a client sends an invalid request message, uses an invalid HTTP protocol
version or sends an invalid `Transfer-Encoding` in the request header, it will
emit an `error` event, send an HTTP error response to the client and
close the connection:
```php
$server->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
The server will also emit an `error` event if you return an invalid
type in the callback function or have a unhandled `Exception` or `Throwable`.
If your callback function throws an `Exception` or `Throwable`,
the `StreamingServer` will emit a `RuntimeException` and add the thrown exception
as previous:
```php
$server->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
if ($e->getPrevious() !== null) {
$previousException = $e->getPrevious();
echo $previousException->getMessage() . PHP_EOL;
}
});
```
Note that the request object can also emit an error.
Check out [request](#request) for more details.
### Request
As seen above, the [`Server`](#server) and [`StreamingServer`](#streamingserver)
classes are responsible for handling incoming connections and then processing
each incoming HTTP request.
The request object will be processed once the request has
been received by the client.
This request object implements the
[PSR-7 ServerRequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#321-psrhttpmessageserverrequestinterface)
which in turn extends the
[PSR-7 RequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#32-psrhttpmessagerequestinterface)
and will be passed to the callback function like this.
```php
$server = new Server(function (ServerRequestInterface $request) {
$body = "The method of the request is: " . $request->getMethod();
$body .= "The requested path is: " . $request->getUri()->getPath();
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$body
);
});
```
For more details about the request object, also check out the documentation of
[PSR-7 ServerRequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#321-psrhttpmessageserverrequestinterface)
and
[PSR-7 RequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#32-psrhttpmessagerequestinterface).
#### Request parameters
The `getServerParams(): mixed[]` method can be used to
get server-side parameters similar to the `$_SERVER` variable.
The following parameters are currently available:
* `REMOTE_ADDR`
The IP address of the request sender
* `REMOTE_PORT`
Port of the request sender
* `SERVER_ADDR`
The IP address of the server
* `SERVER_PORT`
The port of the server
* `REQUEST_TIME`
Unix timestamp when the complete request header has been received,
as integer similar to `time()`
* `REQUEST_TIME_FLOAT`
Unix timestamp when the complete request header has been received,
as float similar to `microtime(true)`
* `HTTPS`
Set to 'on' if the request used HTTPS, otherwise it won't be set
```php
$server = new Server(function (ServerRequestInterface $request) {
$body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR'];
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$body
);
});
```
See also [example #3](examples).
> Advanced: Note that address parameters will not be set if you're listening on
a Unix domain socket (UDS) path as this protocol lacks the concept of
host/port.
#### Query parameters
The `getQueryParams(): array` method can be used to get the query parameters
similiar to the `$_GET` variable.
```php
$server = new Server(function (ServerRequestInterface $request) {
$queryParams = $request->getQueryParams();
$body = 'The query parameter "foo" is not set. Click the following link ';
$body .= 'to use query parameter in your request';
if (isset($queryParams['foo'])) {
$body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']);
}
return new Response(
200,
array(
'Content-Type' => 'text/html'
),
$body
);
});
```
The response in the above example will return a response body with a link.
The URL contains the query parameter `foo` with the value `bar`.
Use [`htmlentities`](http://php.net/manual/en/function.htmlentities.php)
like in this example to prevent
[Cross-Site Scripting (abbreviated as XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting).
See also [example #4](examples).
#### Streaming request
If you're using the [`Server`](#server), then the request object will be
buffered and parsed in memory and contains the full request body.
This includes the parsed request body and any file uploads.
If you're using the advanced [`StreamingServer`](#streamingserver), the
request object will be processed once the request headers have been received.
This means that this happens irrespective of (i.e. *before*) receiving the
(potentially much larger) request body.
While this may be uncommon in the PHP ecosystem, this is actually a very powerful
approach that gives you several advantages not otherwise possible:
* React to requests *before* receiving a large request body,
such as rejecting an unauthenticated request or one that exceeds allowed
message lengths (file uploads).
* Start processing parts of the request body before the remainder of the request
body arrives or if the sender is slowly streaming data.
* Process a large request body without having to buffer anything in memory,
such as accepting a huge file upload or possibly unlimited request body stream.
The `getBody()` method can be used to access the request body stream.
In the default streaming mode, this method returns a stream instance that implements both the
[PSR-7 StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface)
and the [ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface).
However, most of the `PSR-7 StreamInterface` methods have been
designed under the assumption of being in control of the request body.
Given that this does not apply to this server, the following
`PSR-7 StreamInterface` methods are not used and SHOULD NOT be called:
`tell()`, `eof()`, `seek()`, `rewind()`, `write()` and `read()`.
If this is an issue for your use case and/or you want to access uploaded files,
it's highly recommended to use the
[`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) instead.
The `ReactPHP ReadableStreamInterface` gives you access to the incoming
request body as the individual chunks arrive:
```php
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Promise(function ($resolve, $reject) use ($request) {
$contentLength = 0;
$request->getBody()->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});
$request->getBody()->on('end', function () use ($resolve, &$contentLength){
$response = new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"The length of the submitted request body is: " . $contentLength
);
$resolve($response);
});
// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
$request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) {
$response = new Response(
400,
array(
'Content-Type' => 'text/plain'
),
"An error occured while reading at length: " . $contentLength
);
$resolve($response);
});
});
});
```
The above example simply counts the number of bytes received in the request body.
This can be used as a skeleton for buffering or processing the request body.
See also [example #9](examples) for more details.
The `data` event will be emitted whenever new data is available on the request
body stream.
The server also automatically takes care of decoding any incoming requests using
`Transfer-Encoding: chunked` and will only emit the actual payload as data.
The `end` event will be emitted when the request body stream terminates
successfully, i.e. it was read until its expected end.
The `error` event will be emitted in case the request stream contains invalid
data for `Transfer-Encoding: chunked` or when the connection closes before
the complete request stream has been received.
The server will automatically stop reading from the connection and discard all
incoming data instead of closing it.
A response message can still be sent (unless the connection is already closed).
A `close` event will be emitted after an `error` or `end` event.
For more details about the request body stream, check out the documentation of
[ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface).
The `getSize(): ?int` method can be used if you only want to know the request
body size.
This method returns the complete size of the request body as defined by the
message boundaries.
This value may be `0` if the request message does not contain a request body
(such as a simple `GET` request).
Note that this value may be `null` if the request body size is unknown in
advance because the request message uses `Transfer-Encoding: chunked`.
```php
$server = new StreamingServer(function (ServerRequestInterface $request) {
$size = $request->getBody()->getSize();
if ($size === null) {
$body = 'The request does not contain an explicit length.';
$body .= 'This example does not accept chunked transfer encoding.';
return new Response(
411,
array(
'Content-Type' => 'text/plain'
),
$body
);
}
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Request body size: " . $size . " bytes\n"
);
});
```
#### Request method
Note that the server supports *any* request method (including custom and non-
standard ones) and all request-target formats defined in the HTTP specs for each
respective method, including *normal* `origin-form` requests as well as
proxy requests in `absolute-form` and `authority-form`.
The `getUri(): UriInterface` method can be used to get the effective request
URI which provides you access to individiual URI components.
Note that (depending on the given `request-target`) certain URI components may
or may not be present, for example the `getPath(): string` method will return
an empty string for requests in `asterisk-form` or `authority-form`.
Its `getHost(): string` method will return the host as determined by the
effective request URI, which defaults to the local socket address if a HTTP/1.0
client did not specify one (i.e. no `Host` header).
Its `getScheme(): string` method will return `http` or `https` depending
on whether the request was made over a secure TLS connection to the target host.
The `Host` header value will be sanitized to match this host component plus the
port component only if it is non-standard for this URI scheme.
You can use `getMethod(): string` and `getRequestTarget(): string` to
check this is an accepted request and may want to reject other requests with
an appropriate error code, such as `400` (Bad Request) or `405` (Method Not
Allowed).
> The `CONNECT` method is useful in a tunneling setup (HTTPS proxy) and not
something most HTTP servers would want to care about.
Note that if you want to handle this method, the client MAY send a different
request-target than the `Host` header value (such as removing default ports)
and the request-target MUST take precendence when forwarding.
#### Cookie parameters
The `getCookieParams(): string[]` method can be used to
get all cookies sent with the current request.
```php
$server = new Server(function (ServerRequestInterface $request) {
$key = 'react\php';
if (isset($request->getCookieParams()[$key])) {
$body = "Your cookie value is: " . $request->getCookieParams()[$key];
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$body
);
}
return new Response(
200,
array(
'Content-Type' => 'text/plain',
'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more')
),
"Your cookie has been set."
);
});
```
The above example will try to set a cookie on first access and
will try to print the cookie value on all subsequent tries.
Note how the example uses the `urlencode()` function to encode
non-alphanumeric characters.
This encoding is also used internally when decoding the name and value of cookies
(which is in line with other implementations, such as PHP's cookie functions).
See also [example #5](examples) for more details.
### Response
The callback function passed to the constructor of the [`Server`](#server) or
advanced [`StreamingServer`](#server) is responsible for processing the request
and returning a response, which will be delivered to the client.
This function MUST return an instance implementing
[PSR-7 ResponseInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#33-psrhttpmessageresponseinterface)
object or a
[ReactPHP Promise](https://github.com/reactphp/promise#reactpromise)
which will resolve a `PSR-7 ResponseInterface` object.
You will find a `Response` class
which implements the `PSR-7 ResponseInterface` in this project.
We use instantiation of this class in our projects,
but feel free to use any implemantation of the
`PSR-7 ResponseInterface` you prefer.
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello World!\n"
);
});
```
#### Deferred response
The example above returns the response directly, because it needs
no time to be processed.
Using a database, the file system or long calculations
(in fact every action that will take >=1ms) to create your
response, will slow down the server.
To prevent this you SHOULD use a
[ReactPHP Promise](https://github.com/reactphp/promise#reactpromise).
This example shows how such a long-term action could look like:
```php
$server = new Server(function (ServerRequestInterface $request) use ($loop) {
return new Promise(function ($resolve, $reject) use ($loop) {
$loop->addTimer(1.5, function() use ($resolve) {
$response = new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello world"
);
$resolve($response);
});
});
});
```
The above example will create a response after 1.5 second.
This example shows that you need a promise,
if your response needs time to created.
The `ReactPHP Promise` will resolve in a `Response` object when the request
body ends.
If the client closes the connection while the promise is still pending, the
promise will automatically be cancelled.
The promise cancellation handler can be used to clean up any pending resources
allocated in this case (if applicable).
If a promise is resolved after the client closes, it will simply be ignored.
#### Streaming response
The `Response` class in this project supports to add an instance which implements the
[ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface)
for the response body.
So you are able stream data directly into the response body.
Note that other implementations of the `PSR-7 ResponseInterface` likely
only support strings.
```php
$server = new Server(function (ServerRequestInterface $request) use ($loop) {
$stream = new ThroughStream();
$timer = $loop->addPeriodicTimer(0.5, function () use ($stream) {
$stream->emit('data', array(microtime(true) . PHP_EOL));
});
$loop->addTimer(5, function() use ($loop, $timer, $stream) {
$loop->cancelTimer($timer);
$stream->emit('end');
});
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$stream
);
});
```
The above example will emit every 0.5 seconds the current Unix timestamp
with microseconds as float to the client and will end after 5 seconds.
This is just a example you could use of the streaming,
you could also send a big amount of data via little chunks
or use it for body data that needs to calculated.
If the request handler resolves with a response stream that is already closed,
it will simply send an empty response body.
If the client closes the connection while the stream is still open, the
response stream will automatically be closed.
If a promise is resolved with a streaming body after the client closes, the
response stream will automatically be closed.
The `close` event can be used to clean up any pending resources allocated
in this case (if applicable).
> Note that special care has to be taken if you use a body stream instance that
implements ReactPHP's
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
(such as the `ThroughStream` in the above example).
>
> For *most* cases, this will simply only consume its readable side and forward
(send) any data that is emitted by the stream, thus entirely ignoring the
writable side of the stream.
If however this is either a `101` (Switching Protocols) response or a `2xx`
(Successful) response to a `CONNECT` method, it will also *write* data to the
writable side of the stream.
This can be avoided by either rejecting all requests with the `CONNECT`
method (which is what most *normal* origin HTTP servers would likely do) or
or ensuring that only ever an instance of `ReadableStreamInterface` is
used.
>
> The `101` (Switching Protocols) response code is useful for the more advanced
`Upgrade` requests, such as upgrading to the WebSocket protocol or
implementing custom protocol logic that is out of scope of the HTTP specs and
this HTTP library.
If you want to handle the `Upgrade: WebSocket` header, you will likely want
to look into using [Ratchet](http://socketo.me/) instead.
If you want to handle a custom protocol, you will likely want to look into the
[HTTP specs](https://tools.ietf.org/html/rfc7230#section-6.7) and also see
[examples #31 and #32](examples) for more details.
In particular, the `101` (Switching Protocols) response code MUST NOT be used
unless you send an `Upgrade` response header value that is also present in
the corresponding HTTP/1.1 `Upgrade` request header value.
The server automatically takes care of sending a `Connection: upgrade`
header value in this case, so you don't have to.
>
> The `CONNECT` method is useful in a tunneling setup (HTTPS proxy) and not
something most origin HTTP servers would want to care about.
The HTTP specs define an opaque "tunneling mode" for this method and make no
use of the message body.
For consistency reasons, this library uses a `DuplexStreamInterface` in the
response body for tunneled application data.
This implies that that a `2xx` (Successful) response to a `CONNECT` request
can in fact use a streaming response body for the tunneled application data,
so that any raw data the client sends over the connection will be piped
through the writable stream for consumption.
Note that while the HTTP specs make no use of the request body for `CONNECT`
requests, one may still be present. Normal request body processing applies
here and the connection will only turn to "tunneling mode" after the request
body has been processed (which should be empty in most cases).
See also [example #22](examples) for more details.
#### Response length
If the response body is a `string`, a `Content-Length` header will be added
automatically.
If the response body is a ReactPHP `ReadableStreamInterface` and you do not
specify a `Content-Length` header, outgoing HTTP/1.1 response messages will
automatically use `Transfer-Encoding: chunked` and send the respective header
automatically.
The server is responsible for handling `Transfer-Encoding`, so you SHOULD NOT
pass this header yourself.
If you know the length of your stream body, you MAY specify it like this instead:
```php
$stream = new ThroughStream();
$server = new Server(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(
'Content-Length' => '5',
'Content-Type' => 'text/plain',
),
$stream
);
});
```
Any response to a `HEAD` request and any response with a `1xx` (Informational),
`204` (No Content) or `304` (Not Modified) status code will *not* include a
message body as per the HTTP specs.
This means that your callback does not have to take special care of this and any
response body will simply be ignored.
Similarly, any `2xx` (Successful) response to a `CONNECT` request, any response
with a `1xx` (Informational) or `204` (No Content) status code will *not*
include a `Content-Length` or `Transfer-Encoding` header as these do not apply
to these messages.
Note that a response to a `HEAD` request and any response with a `304` (Not
Modified) status code MAY include these headers even though
the message does not contain a response body, because these header would apply
to the message if the same request would have used an (unconditional) `GET`.
#### Invalid response
An invalid return value or an unhandled `Exception` or `Throwable` in the code
of the callback function, will result in an `500 Internal Server Error` message.
Make sure to catch `Exceptions` or `Throwables` to create own response messages.
#### Default response headers
After the return in the callback function the response will be processed by the
[`Server`](#server) or [`StreamingServer`](#streamingserver) respectively.
They will add the protocol version of the request, so you don't have to.
A `Date` header will be automatically added with the system date and time if none is given.
You can add a custom `Date` header yourself like this:
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Date' => date('D, d M Y H:i:s T')
)
);
});
```
If you don't have a appropriate clock to rely on, you should
unset this header with an empty string:
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Date' => ''
)
);
});
```
Note that it will automatically assume a `X-Powered-By: react/alpha` header
unless your specify a custom `X-Powered-By` header yourself:
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'X-Powered-By' => 'PHP 3'
)
);
});
```
If you do not want to send this header at all, you can use an empty string as
value like this:
```php
$server = new Server(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'X-Powered-By' => ''
)
);
});
```
Note that persistent connections (`Connection: keep-alive`) are currently
not supported.
As such, HTTP/1.1 response messages will automatically include a
`Connection: close` header, irrespective of what header values are
passed explicitly.
### Middleware
As documented above, the [`Server`](#server) and advanced
[`StreamingServer`](#streamingserver) accept a single
request handler argument that is responsible for processing an incoming
HTTP request and then creating and returning an outgoing HTTP response.
Many common use cases involve validating, processing, manipulating the incoming
HTTP request before passing it to the final business logic request handler.
As such, this project supports the concept of middleware request handlers.
A middleware request handler is expected to adhere the following rules:
* It is a valid `callable`.
* It accepts `ServerRequestInterface` as first argument and an optional
`callable` as second argument.
* It returns either:
* An instance implementing `ResponseInterface` for direct consumption.
* Any promise which can be consumed by
[`Promise\resolve()`](http://reactphp.org/promise/#resolve) resolving to a
`ResponseInterface` for deferred consumption.
* It MAY throw an `Exception` (or return a rejected promise) in order to
signal an error condition and abort the chain.
* It calls `$next($request)` to continue processing the next middleware
request handler or returns explicitly without calling `$next` to
abort the chain.
* The `$next` request handler (recursively) invokes the next request
handler from the chain with the same logic as above and returns (or throws)
as above.
* The `$request` may be modified prior to calling `$next($request)` to
change the incoming request the next middleware operates on.
* The `$next` return value may be consumed to modify the outgoing response.
* The `$next` request handler MAY be called more than once if you want to
implement custom "retry" logic etc.
Note that this very simple definition allows you to use either anonymous
functions or any classes that use the magic `__invoke()` method.
This allows you to easily create custom middleware request handlers on the fly
or use a class based approach to ease using existing middleware implementations.
While this project does provide the means to *use* middleware implementations,
it does not aim to *define* how middleware implementations should look like.
We realize that there's a vivid ecosystem of middleware implementations and
ongoing effort to standardize interfaces between these with
[PSR-15](https://www.php-fig.org/psr/psr-15/) (HTTP Server Request Handlers)
and support this goal.
As such, this project only bundles a few middleware implementations that are
required to match PHP's request behavior (see below) and otherwise actively
encourages [Third-Party Middleware](#third-party-middleware) implementations.
In order to use middleware request handlers, simply pass an array with all
callables as defined above to the [`Server`](#server) or
[`StreamingServer`](#streamingserver) respectively.
The following example adds a middleware request handler that adds the current time to the request as a
header (`Request-Time`) and a final request handler that always returns a 200 code without a body:
```php
$server = new Server(array(
function (ServerRequestInterface $request, callable $next) {
$request = $request->withHeader('Request-Time', time());
return $next($request);
},
function (ServerRequestInterface $request) {
return new Response(200);
}
));
```
> Note how the middleware request handler and the final request handler have a
very simple (and similar) interface. The only difference is that the final
request handler does not receive a `$next` handler.
Similarly, you can use the result of the `$next` middleware request handler
function to modify the outgoing response.
Note that as per the above documentation, the `$next` middleware request handler may return a
`ResponseInterface` directly or one wrapped in a promise for deferred
resolution.
In order to simplify handling both paths, you can simply wrap this in a
[`Promise\resolve()`](http://reactphp.org/promise/#resolve) call like this:
```php
$server = new Server(array(
function (ServerRequestInterface $request, callable $next) {
$promise = React\Promise\resolve($next($request));
return $promise->then(function (ResponseInterface $response) {
return $response->withHeader('Content-Type', 'text/html');
});
},
function (ServerRequestInterface $request) {
return new Response(200);
}
));
```
Note that the `$next` middleware request handler may also throw an
`Exception` (or return a rejected promise) as described above.
The previous example does not catch any exceptions and would thus signal an
error condition to the `Server`.
Alternatively, you can also catch any `Exception` to implement custom error
handling logic (or logging etc.) by wrapping this in a
[`Promise`](http://reactphp.org/promise/#promise) like this:
```php
$server = new Server(array(
function (ServerRequestInterface $request, callable $next) {
$promise = new React\Promise\Promise(function ($resolve) use ($next, $request) {
$resolve($next($request));
});
return $promise->then(null, function (Exception $e) {
return new Response(
500,
array(),
'Internal error: ' . $e->getMessage()
);
});
},
function (ServerRequestInterface $request) {
if (mt_rand(0, 1) === 1) {
throw new RuntimeException('Database error');
}
return new Response(200);
}
));
```
#### LimitConcurrentRequestsMiddleware
The `LimitConcurrentRequestsMiddleware` can be used to
limit how many next handlers can be executed concurrently.
If this middleware is invoked, it will check if the number of pending
handlers is below the allowed limit and then simply invoke the next handler
and it will return whatever the next handler returns (or throws).
If the number of pending handlers exceeds the allowed limit, the request will
be queued (and its streaming body will be paused) and it will return a pending
promise.
Once a pending handler returns (or throws), it will pick the oldest request
from this queue and invokes the next handler (and its streaming body will be
resumed).
The following example shows how this middleware can be used to ensure no more
than 10 handlers will be invoked at once:
```php
$server = new Server(array(
new LimitConcurrentRequestsMiddleware(10),
$handler
));
```
Similarly, this middleware is often used in combination with the
[`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
to limit the total number of requests that can be buffered at once:
```php
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
new RequestBodyParserMiddleware(),
$handler
));
```
More sophisticated examples include limiting the total number of requests
that can be buffered at once and then ensure the actual request handler only
processes one request after another without any concurrency:
```php
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
new RequestBodyParserMiddleware(),
new LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
$handler
));
```
#### RequestBodyBufferMiddleware
One of the built-in middleware is the `RequestBodyBufferMiddleware` which
can be used to buffer the whole incoming request body in memory.
This can be useful if full PSR-7 compatibility is needed for the request handler
and the default streaming request body handling is not needed.
The constructor accepts one optional argument, the maximum request body size.
When one isn't provided it will use `post_max_size` (default 8 MiB) from PHP's
configuration.
(Note that the value from your matching SAPI will be used, which is the CLI
configuration in most cases.)
Any incoming request that has a request body that exceeds this limit will be
accepted, but its request body will be discarded (empty request body).
This is done in order to avoid having to keep an incoming request with an
excessive size (for example, think of a 2 GB file upload) in memory.
This allows the next middleware handler to still handle this request, but it
will see an empty request body.
This is similar to PHP's default behavior, where the body will not be parsed
if this limit is exceeded. However, unlike PHP's default behavior, the raw
request body is not available via `php://input`.
The `RequestBodyBufferMiddleware` will buffer requests with bodies of known size
(i.e. with `Content-Length` header specified) as well as requests with bodies of
unknown size (i.e. with `Transfer-Encoding: chunked` header).
All requests will be buffered in memory until the request body end has
been reached and then call the next middleware handler with the complete,
buffered request.
Similarly, this will immediately invoke the next middleware handler for requests
that have an empty request body (such as a simple `GET` request) and requests
that are already buffered (such as due to another middleware).
Note that the given buffer size limit is applied to each request individually.
This means that if you allow a 2 MiB limit and then receive 1000 concurrent
requests, up to 2000 MiB may be allocated for these buffers alone.
As such, it's highly recommended to use this along with the
[`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) (see above) to limit
the total number of concurrent requests.
Usage:
```php
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
function (ServerRequestInterface $request) {
// The body from $request->getBody() is now fully available without the need to stream it
return new Response(200);
},
));
```
#### RequestBodyParserMiddleware
The `RequestBodyParserMiddleware` takes a fully buffered request body
(generally from [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware)),
and parses the form values and file uploads from the incoming HTTP request body.
This middleware handler takes care of applying values from HTTP
requests that use `Content-Type: application/x-www-form-urlencoded` or
`Content-Type: multipart/form-data` to resemble PHP's default superglobals
`$_POST` and `$_FILES`.
Instead of relying on these superglobals, you can use the
`$request->getParsedBody()` and `$request->getUploadedFiles()` methods
as defined by PSR-7.
Accordingly, each file upload will be represented as instance implementing [`UploadedFileInterface`](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#36-psrhttpmessageuploadedfileinterface).
Due to its blocking nature, the `moveTo()` method is not available and throws
a `RuntimeException` instead.
You can use `$contents = (string)$file->getStream();` to access the file
contents and persist this to your favorite data store.
```php
$handler = function (ServerRequestInterface $request) {
// If any, parsed form fields are now available from $request->getParsedBody()
$body = $request->getParsedBody();
$name = isset($body['name']) ? $body['name'] : 'unnamed';
$files = $request->getUploadedFiles();
$avatar = isset($files['avatar']) ? $files['avatar'] : null;
if ($avatar instanceof UploadedFileInterface) {
if ($avatar->getError() === UPLOAD_ERR_OK) {
$uploaded = $avatar->getSize() . ' bytes';
} elseif ($avatar->getError() === UPLOAD_ERR_INI_SIZE) {
$uploaded = 'file too large';
} else {
$uploaded = 'with error';
}
} else {
$uploaded = 'nothing';
}
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$name . ' uploaded ' . $uploaded
);
};
$server = new StreamingServer(array((
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
new RequestBodyParserMiddleware(),
$handler
));
```
See also [example #12](examples) for more details.
By default, this middleware respects the
[`upload_max_filesize`](http://php.net/manual/en/ini.core.php#ini.upload-max-filesize)
(default `2M`) ini setting.
Files that exceed this limit will be rejected with an `UPLOAD_ERR_INI_SIZE` error.
You can control the maximum filesize for each individual file upload by
explicitly passing the maximum filesize in bytes as the first parameter to the
constructor like this:
```php
new RequestBodyParserMiddleware(8 * 1024 * 1024); // 8 MiB limit per file
```
By default, this middleware respects the
[`file_uploads`](http://php.net/manual/en/ini.core.php#ini.file-uploads)
(default `1`) and
[`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads)
(default `20`) ini settings.
These settings control if any and how many files can be uploaded in a single request.
If you upload more files in a single request, additional files will be ignored
and the `getUploadedFiles()` method returns a truncated array.
Note that upload fields left blank on submission do not count towards this limit.
You can control the maximum number of file uploads per request by explicitly
passing the second parameter to the constructor like this:
```php
new RequestBodyParserMiddleware(10 * 1024, 100); // 100 files with 10 KiB each
```
> Note that this middleware handler simply parses everything that is already
buffered in the request body.
It is imperative that the request body is buffered by a prior middleware
handler as given in the example above.
This previous middleware handler is also responsible for rejecting incoming
requests that exceed allowed message sizes (such as big file uploads).
The [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) used above
simply discards excessive request bodies, resulting in an empty body.
If you use this middleware without buffering first, it will try to parse an
empty (streaming) body and may thus assume an empty data structure.
See also [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) for
more details.
> PHP's `MAX_FILE_SIZE` hidden field is respected by this middleware.
Files that exceed this limit will be rejected with an `UPLOAD_ERR_FORM_SIZE` error.
> This middleware respects the
[`max_input_vars`](http://php.net/manual/en/info.configuration.php#ini.max-input-vars)
(default `1000`) and
[`max_input_nesting_level`](http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level)
(default `64`) ini settings.
> Note that this middleware ignores the
[`enable_post_data_reading`](http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading)
(default `1`) ini setting because it makes little sense to respect here and
is left up to higher-level implementations.
If you want to respect this setting, you have to check its value and
effectively avoid using this middleware entirely.
#### Third-Party Middleware
While this project does provide the means to *use* middleware implementations
(see above), it does not aim to *define* how middleware implementations should
look like. We realize that there's a vivid ecosystem of middleware
implementations and ongoing effort to standardize interfaces between these with
[PSR-15](https://www.php-fig.org/psr/psr-15/) (HTTP Server Request Handlers)
and support this goal.
As such, this project only bundles a few middleware implementations that are
required to match PHP's request behavior (see above) and otherwise actively
encourages third-party middleware implementations.
While we would love to support PSR-15 directy in `react/http`, we understand
that this interface does not specifically target async APIs and as such does
not take advantage of promises for [deferred responses](#deferred-response).
The gist of this is that where PSR-15 enforces a `ResponseInterface` return
value, we also accept a `PromiseInterface`.
As such, we suggest using the external
[PSR-15 middleware adapter](https://github.com/friends-of-reactphp/http-middleware-psr15-adapter)
that uses on the fly monkey patching of these return values which makes using
most PSR-15 middleware possible with this package without any changes required.
Other than that, you can also use the above [middleware definition](#middleware)
to create custom middleware. A non-exhaustive list of third-party middleware can
be found at the [middleware wiki](https://github.com/reactphp/http/wiki/Middleware).
If you build or know a custom middleware, make sure to let the world know and
feel free to add it to this list.
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
$ composer require react/http:^0.8.3
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
HHVM.
It's *highly recommended to use PHP 7+* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org):
```bash
$ composer install
```
To run the test suite, go to the project root and run:
```bash
$ php vendor/bin/phpunit
```
## License
MIT, see [LICENSE file](LICENSE).
http-0.8.3/composer.json 0000664 0000000 0000000 00000001354 13263421677 0015241 0 ustar 00root root 0000000 0000000 {
"name": "react/http",
"description": "Event-driven, streaming plaintext HTTP and secure HTTPS server for ReactPHP",
"keywords": ["event-driven", "streaming", "HTTP", "HTTPS", "server", "ReactPHP"],
"license": "MIT",
"require": {
"php": ">=5.3.0",
"ringcentral/psr7": "^1.2",
"react/socket": "^1.0 || ^0.8.3",
"react/stream": "^1.0 || ^0.7.1",
"react/promise": "^2.3 || ^1.2.1",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"react/promise-stream": "^1.1"
},
"autoload": {
"psr-4": {
"React\\Http\\": "src"
}
},
"require-dev": {
"clue/block-react": "^1.1",
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
}
}
http-0.8.3/examples/ 0000775 0000000 0000000 00000000000 13263421677 0014332 5 ustar 00root root 0000000 0000000 http-0.8.3/examples/01-hello-world.php 0000664 0000000 0000000 00000001162 13263421677 0017511 0 ustar 00root root 0000000 0000000 'text/plain'
),
"Hello world\n"
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/02-count-visitors.php 0000664 0000000 0000000 00000001247 13263421677 0020276 0 ustar 00root root 0000000 0000000 'text/plain'
),
"Welcome number " . ++$counter . "!\n"
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/03-client-ip.php 0000664 0000000 0000000 00000001262 13263421677 0017150 0 ustar 00root root 0000000 0000000 getServerParams()['REMOTE_ADDR'];
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$body
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/04-query-parameter.php 0000664 0000000 0000000 00000001666 13263421677 0020420 0 ustar 00root root 0000000 0000000 getQueryParams();
$body = 'The query parameter "foo" is not set. Click the following link ';
$body .= 'to use query parameter in your request';
if (isset($queryParams['foo'])) {
$body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']);
}
return new Response(
200,
array(
'Content-Type' => 'text/html'
),
$body
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/05-cookie-handling.php 0000664 0000000 0000000 00000002011 13263421677 0020312 0 ustar 00root root 0000000 0000000 getCookieParams()[$key])) {
$body = "Your cookie value is: " . $request->getCookieParams()[$key];
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$body
);
}
return new Response(
200,
array(
'Content-Type' => 'text/plain',
'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more')
),
"Your cookie has been set."
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/06-sleep.php 0000664 0000000 0000000 00000001604 13263421677 0016377 0 ustar 00root root 0000000 0000000 addTimer(1.5, function() use ($resolve) {
$response = new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"Hello world"
);
$resolve($response);
});
});
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/07-error-handling.php 0000664 0000000 0000000 00000001637 13263421677 0020211 0 ustar 00root root 0000000 0000000 'text/plain'
),
"Hello World!\n"
);
$resolve($response);
});
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/08-stream-response.php 0000664 0000000 0000000 00000002712 13263421677 0020421 0 ustar 00root root 0000000 0000000 getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') {
return new Response(404);
}
$stream = new ThroughStream();
// send some data every once in a while with periodic timer
$timer = $loop->addPeriodicTimer(0.5, function () use ($stream) {
$stream->write(microtime(true) . PHP_EOL);
});
// demo for ending stream after a few seconds
$loop->addTimer(5.0, function() use ($stream) {
$stream->end();
});
// stop timer if stream is closed (such as when connection is closed)
$stream->on('close', function () use ($loop, $timer) {
$loop->cancelTimer($timer);
});
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
$stream
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/09-stream-request.php 0000664 0000000 0000000 00000003563 13263421677 0020261 0 ustar 00root root 0000000 0000000 getBody();
$requestBody->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});
$requestBody->on('end', function () use ($resolve, &$contentLength){
$response = new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"The length of the submitted request body is: " . $contentLength
);
$resolve($response);
});
// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
$requestBody->on('error', function (\Exception $exception) use ($resolve, &$contentLength) {
$response = new Response(
400,
array(
'Content-Type' => 'text/plain'
),
"An error occured while reading at length: " . $contentLength
);
$resolve($response);
});
});
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/11-hello-world-https.php 0000664 0000000 0000000 00000001447 13263421677 0020660 0 ustar 00root root 0000000 0000000 'text/plain'
),
"Hello world!\n"
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$socket = new \React\Socket\SecureServer($socket, $loop, array(
'local_cert' => isset($argv[2]) ? $argv[2] : __DIR__ . '/localhost.pem'
));
$server->listen($socket);
//$socket->on('error', 'printf');
echo 'Listening on ' . str_replace('tls:', 'https:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/12-upload.php 0000664 0000000 0000000 00000010502 13263421677 0016545 0 ustar 00root root 0000000 0000000 getMethod() === 'POST') {
// Take form input values from POST values (for illustration purposes only!)
// Does not actually validate data here
$body = $request->getParsedBody();
$name = isset($body['name']) && is_string($body['name']) ? htmlspecialchars($body['name']) : 'n/a';
$age = isset($body['age']) && is_string($body['age']) ? (int)$body['age'] : 'n/a';
// Show uploaded avatar as image (for illustration purposes only!)
// Real applications should validate the file data to ensure this is
// actually an image and not rely on the client media type.
$avatar = 'n/a';
$uploads = $request->getUploadedFiles();
if (isset($uploads['avatar']) && $uploads['avatar'] instanceof UploadedFileInterface) {
/* @var $file UploadedFileInterface */
$file = $uploads['avatar'];
if ($file->getError() === UPLOAD_ERR_OK) {
// Note that moveFile() is not available due to its blocking nature.
// You can use your favorite data store to simply dump the file
// contents via `(string)$file->getStream()` instead.
// Here, we simply use an inline image to send back to client:
$avatar = ' (' . $file->getSize() . ' bytes)';
} elseif ($file->getError() === UPLOAD_ERR_INI_SIZE) {
$avatar = 'upload exceeds file size limit';
} else {
// Real applications should probably check the error number and
// should print some human-friendly text
$avatar = 'upload error ' . $file->getError();
}
}
$dump = htmlspecialchars(
var_export($request->getParsedBody(), true) .
PHP_EOL .
var_export($request->getUploadedFiles(), true)
);
$body = <<
$dump
BODY;
} else {
$body = <<
BODY;
}
$html = <<
$body
HTML;
return new Response(
200,
array(
'Content-Type' => 'text/html; charset=UTF-8'
),
$html
);
};
// Note how this example explicitly uses the advanced `StreamingServer` to apply
// custom request buffering limits below before running our request handler.
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers, queue otherwise
new RequestBodyBufferMiddleware(8 * 1024 * 1024), // 8 MiB max, ignore body otherwise
new RequestBodyParserMiddleware(100 * 1024, 1), // 1 file with 100 KiB max, reject upload otherwise
$handler
));
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/21-http-proxy.php 0000664 0000000 0000000 00000003470 13263421677 0017425 0 ustar 00root root 0000000 0000000 getRequestTarget(), '://') === false) {
return new Response(
400,
array(
'Content-Type' => 'text/plain'
),
'This is a plain HTTP proxy'
);
}
// prepare outgoing client request by updating request-target and Host header
$host = (string)$request->getUri()->withScheme('')->withPath('')->withQuery('');
$target = (string)$request->getUri()->withScheme('')->withHost('')->withPort(null);
if ($target === '') {
$target = $request->getMethod() === 'OPTIONS' ? '*' : '/';
}
$outgoing = $request->withRequestTarget($target)->withHeader('Host', $host);
// pseudo code only: simply dump the outgoing request as a string
// left up as an exercise: use an HTTP client to send the outgoing request
// and forward the incoming response to the original client request
return new Response(
200,
array(
'Content-Type' => 'text/plain'
),
Psr7\str($outgoing)
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/22-connect-proxy.php 0000664 0000000 0000000 00000003366 13263421677 0020104 0 ustar 00root root 0000000 0000000 getMethod() !== 'CONNECT') {
return new Response(
405,
array(
'Content-Type' => 'text/plain',
'Allow' => 'CONNECT'
),
'This is a HTTP CONNECT (secure HTTPS) proxy'
);
}
// try to connect to given target host
return $connector->connect($request->getRequestTarget())->then(
function (ConnectionInterface $remote) {
// connection established => forward data
return new Response(
200,
array(),
$remote
);
},
function ($e) {
return new Response(
502,
array(
'Content-Type' => 'text/plain'
),
'Unable to connect: ' . $e->getMessage()
);
}
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/31-upgrade-echo.php 0000664 0000000 0000000 00000003206 13263421677 0017630 0 ustar 00root root 0000000 0000000 GET / HTTP/1.1
> Upgrade: echo
>
< HTTP/1.1 101 Switching Protocols
< Upgrade: echo
< Connection: upgrade
<
> hello
< hello
> world
< world
*/
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Factory;
use React\Http\Response;
use React\Http\Server;
use React\Stream\ThroughStream;
require __DIR__ . '/../vendor/autoload.php';
$loop = Factory::create();
// Note how this example uses the `Server` instead of `StreamingServer`.
// The initial incoming request does not contain a body and we upgrade to a
// stream object below.
$server = new Server(function (ServerRequestInterface $request) use ($loop) {
if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') {
return new Response(
426,
array(
'Upgrade' => 'echo'
),
'"Upgrade: echo" required'
);
}
// simply return a duplex ThroughStream here
// it will simply emit any data that is sent to it
// this means that any Upgraded data will simply be sent back to the client
$stream = new ThroughStream();
$loop->addTimer(0, function () use ($stream) {
$stream->write("Hello! Anything you send will be piped back." . PHP_EOL);
});
return new Response(
101,
array(
'Upgrade' => 'echo'
),
$stream
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/32-upgrade-chat.php 0000664 0000000 0000000 00000005052 13263421677 0017633 0 ustar 00root root 0000000 0000000 GET / HTTP/1.1
> Upgrade: chat
>
< HTTP/1.1 101 Switching Protocols
< Upgrade: chat
< Connection: upgrade
<
> hello
< user123: hello
> world
< user123: world
Hint: try this with multiple connections :)
*/
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Factory;
use React\Http\Response;
use React\Http\Server;
use React\Stream\CompositeStream;
use React\Stream\ThroughStream;
require __DIR__ . '/../vendor/autoload.php';
$loop = Factory::create();
// simply use a shared duplex ThroughStream for all clients
// it will simply emit any data that is sent to it
// this means that any Upgraded data will simply be sent back to the client
$chat = new ThroughStream();
// Note how this example uses the `Server` instead of `StreamingServer`.
// The initial incoming request does not contain a body and we upgrade to a
// stream object below.
$server = new Server(function (ServerRequestInterface $request) use ($loop, $chat) {
if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') {
return new Response(
426,
array(
'Upgrade' => 'chat'
),
'"Upgrade: chat" required'
);
}
// user stream forwards chat data and accepts incoming data
$out = $chat->pipe(new ThroughStream());
$in = new ThroughStream();
$stream = new CompositeStream(
$out,
$in
);
// assign some name for this new connection
$username = 'user' . mt_rand();
// send anything that is received to the whole channel
$in->on('data', function ($data) use ($username, $chat) {
$data = trim(preg_replace('/[^\w \.\,\-\!\?]/u', '', $data));
$chat->write($username . ': ' . $data . PHP_EOL);
});
// say hello to new user
$loop->addTimer(0, function () use ($chat, $username, $out) {
$out->write('Welcome to this chat example, ' . $username . '!' . PHP_EOL);
$chat->write($username . ' joined' . PHP_EOL);
});
// send goodbye to channel once connection closes
$stream->on('close', function () use ($username, $chat) {
$chat->write($username . ' left' . PHP_EOL);
});
return new Response(
101,
array(
'Upgrade' => 'chat'
),
$stream
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/99-benchmark-download.php 0000664 0000000 0000000 00000006502 13263421677 0021044 0 ustar 00root root 0000000 0000000 /dev/null
// $ wget http://localhost:8080/10g.bin -O /dev/null
// $ ab -n10 -c10 http://localhost:8080/1g.bin
// $ docker run -it --rm --net=host jordi/ab ab -n10 -c10 http://localhost:8080/1g.bin
use Evenement\EventEmitter;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Factory;
use React\Http\Response;
use React\Http\Server;
use React\Stream\ReadableStreamInterface;
use React\Stream\WritableStreamInterface;
require __DIR__ . '/../vendor/autoload.php';
$loop = Factory::create();
/** A readable stream that can emit a lot of data */
class ChunkRepeater extends EventEmitter implements ReadableStreamInterface
{
private $chunk;
private $count;
private $position = 0;
private $paused = true;
private $closed = false;
public function __construct($chunk, $count)
{
$this->chunk = $chunk;
$this->count = $count;
}
public function pause()
{
$this->paused = true;
}
public function resume()
{
if (!$this->paused || $this->closed) {
return;
}
// keep emitting until stream is paused
$this->paused = false;
while ($this->position < $this->count && !$this->paused) {
++$this->position;
$this->emit('data', array($this->chunk));
}
// end once the last chunk has been written
if ($this->position >= $this->count) {
$this->emit('end');
$this->close();
}
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
return;
}
public function isReadable()
{
return !$this->closed;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->count = 0;
$this->paused = true;
$this->emit('close');
}
public function getSize()
{
return strlen($this->chunk) * $this->count;
}
}
// Note how this example still uses `Server` instead of `StreamingServer`.
// The `StreamingServer` is only required for streaming *incoming* requests.
$server = new Server(function (ServerRequestInterface $request) use ($loop) {
switch ($request->getUri()->getPath()) {
case '/':
return new Response(
200,
array(
'Content-Type' => 'text/html'
),
'1g.bin 10g.bin'
);
case '/1g.bin':
$stream = new ChunkRepeater(str_repeat('.', 1000000), 1000);
break;
case '/10g.bin':
$stream = new ChunkRepeater(str_repeat('.', 1000000), 10000);
break;
default:
return new Response(404);
}
$loop->addTimer(0, array($stream, 'resume'));
return new Response(
200,
array(
'Content-Type' => 'application/octet-data',
'Content-Length' => $stream->getSize()
),
$stream
);
});
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
http-0.8.3/examples/localhost.pem 0000664 0000000 0000000 00000005635 13263421677 0017036 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
+EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
81oh1uCH1YPLM29hPyaohxL8
-----END PRIVATE KEY-----
http-0.8.3/phpunit.xml.dist 0000664 0000000 0000000 00000001232 13263421677 0015665 0 ustar 00root root 0000000 0000000
./tests/./src/
http-0.8.3/src/ 0000775 0000000 0000000 00000000000 13263421677 0013303 5 ustar 00root root 0000000 0000000 http-0.8.3/src/Io/ 0000775 0000000 0000000 00000000000 13263421677 0013652 5 ustar 00root root 0000000 0000000 http-0.8.3/src/Io/ChunkedDecoder.php 0000664 0000000 0000000 00000011723 13263421677 0017236 0 ustar 00root root 0000000 0000000 input = $input;
$this->input->on('data', array($this, 'handleData'));
$this->input->on('end', array($this, 'handleEnd'));
$this->input->on('error', array($this, 'handleError'));
$this->input->on('close', array($this, 'close'));
}
public function isReadable()
{
return !$this->closed && $this->input->isReadable();
}
public function pause()
{
$this->input->pause();
}
public function resume()
{
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->buffer = '';
$this->closed = true;
$this->input->close();
$this->emit('close');
$this->removeAllListeners();
}
/** @internal */
public function handleEnd()
{
if (!$this->closed) {
$this->handleError(new Exception('Unexpected end event'));
}
}
/** @internal */
public function handleError(Exception $e)
{
$this->emit('error', array($e));
$this->close();
}
/** @internal */
public function handleData($data)
{
$this->buffer .= $data;
while ($this->buffer !== '') {
if (!$this->headerCompleted) {
$positionCrlf = strpos($this->buffer, static::CRLF);
if ($positionCrlf === false) {
// Header shouldn't be bigger than 1024 bytes
if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
$this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
}
return;
}
$header = strtolower((string)substr($this->buffer, 0, $positionCrlf));
$hexValue = $header;
if (strpos($header, ';') !== false) {
$array = explode(';', $header);
$hexValue = $array[0];
}
if ($hexValue !== '') {
$hexValue = ltrim($hexValue, "0");
if ($hexValue === '') {
$hexValue = "0";
}
}
$this->chunkSize = hexdec($hexValue);
if (dechex($this->chunkSize) !== $hexValue) {
$this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
return;
}
$this->buffer = (string)substr($this->buffer, $positionCrlf + 2);
$this->headerCompleted = true;
if ($this->buffer === '') {
return;
}
}
$chunk = (string)substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
if ($chunk !== '') {
$this->transferredSize += strlen($chunk);
$this->emit('data', array($chunk));
$this->buffer = (string)substr($this->buffer, strlen($chunk));
}
$positionCrlf = strpos($this->buffer, static::CRLF);
if ($positionCrlf === 0) {
if ($this->chunkSize === 0) {
$this->emit('end');
$this->close();
return;
}
$this->chunkSize = 0;
$this->headerCompleted = false;
$this->transferredSize = 0;
$this->buffer = (string)substr($this->buffer, 2);
}
if ($positionCrlf !== 0 && $this->chunkSize === $this->transferredSize && strlen($this->buffer) > 2) {
// the first 2 characters are not CLRF, send error event
$this->handleError(new Exception('Chunk does not end with a CLRF'));
return;
}
if ($positionCrlf !== 0 && strlen($this->buffer) < 2) {
// No CLRF found, wait for additional data which could be a CLRF
return;
}
}
}
}
http-0.8.3/src/Io/ChunkedEncoder.php 0000664 0000000 0000000 00000004611 13263421677 0017246 0 ustar 00root root 0000000 0000000 input = $input;
$this->input->on('data', array($this, 'handleData'));
$this->input->on('end', array($this, 'handleEnd'));
$this->input->on('error', array($this, 'handleError'));
$this->input->on('close', array($this, 'close'));
}
public function isReadable()
{
return !$this->closed && $this->input->isReadable();
}
public function pause()
{
$this->input->pause();
}
public function resume()
{
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->input->close();
$this->emit('close');
$this->removeAllListeners();
}
/** @internal */
public function handleData($data)
{
if ($data === '') {
return;
}
$completeChunk = $this->createChunk($data);
$this->emit('data', array($completeChunk));
}
/** @internal */
public function handleError(\Exception $e)
{
$this->emit('error', array($e));
$this->close();
}
/** @internal */
public function handleEnd()
{
$this->emit('data', array("0\r\n\r\n"));
if (!$this->closed) {
$this->emit('end');
$this->close();
}
}
/**
* @param string $data - string to be transformed in an valid
* HTTP encoded chunk string
* @return string
*/
private function createChunk($data)
{
$byteSize = dechex(strlen($data));
$chunkBeginning = $byteSize . "\r\n";
return $chunkBeginning . $data . "\r\n";
}
}
http-0.8.3/src/Io/CloseProtectionStream.php 0000664 0000000 0000000 00000005405 13263421677 0020657 0 ustar 00root root 0000000 0000000 input = $input;
$this->input->on('data', array($this, 'handleData'));
$this->input->on('end', array($this, 'handleEnd'));
$this->input->on('error', array($this, 'handleError'));
$this->input->on('close', array($this, 'close'));
}
public function isReadable()
{
return !$this->closed && $this->input->isReadable();
}
public function pause()
{
if ($this->closed) {
return;
}
$this->paused = true;
$this->input->pause();
}
public function resume()
{
if ($this->closed) {
return;
}
$this->paused = false;
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
// stop listening for incoming events
$this->input->removeListener('data', array($this, 'handleData'));
$this->input->removeListener('error', array($this, 'handleError'));
$this->input->removeListener('end', array($this, 'handleEnd'));
$this->input->removeListener('close', array($this, 'close'));
// resume the stream to ensure we discard everything from incoming connection
if ($this->paused) {
$this->paused = false;
$this->input->resume();
}
$this->emit('close');
$this->removeAllListeners();
}
/** @internal */
public function handleData($data)
{
$this->emit('data', array($data));
}
/** @internal */
public function handleEnd()
{
$this->emit('end');
$this->close();
}
/** @internal */
public function handleError(\Exception $e)
{
$this->emit('error', array($e));
}
}
http-0.8.3/src/Io/HttpBodyStream.php 0000664 0000000 0000000 00000007527 13263421677 0017307 0 ustar 00root root 0000000 0000000 input = $input;
$this->size = $size;
$this->input->on('data', array($this, 'handleData'));
$this->input->on('end', array($this, 'handleEnd'));
$this->input->on('error', array($this, 'handleError'));
$this->input->on('close', array($this, 'close'));
}
public function isReadable()
{
return !$this->closed && $this->input->isReadable();
}
public function pause()
{
$this->input->pause();
}
public function resume()
{
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->input->close();
$this->emit('close');
$this->removeAllListeners();
}
public function getSize()
{
return $this->size;
}
/** @ignore */
public function __toString()
{
return '';
}
/** @ignore */
public function detach()
{
return null;
}
/** @ignore */
public function tell()
{
throw new \BadMethodCallException();
}
/** @ignore */
public function eof()
{
throw new \BadMethodCallException();
}
/** @ignore */
public function isSeekable()
{
return false;
}
/** @ignore */
public function seek($offset, $whence = SEEK_SET)
{
throw new \BadMethodCallException();
}
/** @ignore */
public function rewind()
{
throw new \BadMethodCallException();
}
/** @ignore */
public function isWritable()
{
return false;
}
/** @ignore */
public function write($string)
{
throw new \BadMethodCallException();
}
/** @ignore */
public function read($length)
{
throw new \BadMethodCallException();
}
/** @ignore */
public function getContents()
{
return '';
}
/** @ignore */
public function getMetadata($key = null)
{
return null;
}
/** @internal */
public function handleData($data)
{
$this->emit('data', array($data));
}
/** @internal */
public function handleError(\Exception $e)
{
$this->emit('error', array($e));
$this->close();
}
/** @internal */
public function handleEnd()
{
if (!$this->closed) {
$this->emit('end');
$this->close();
}
}
}
http-0.8.3/src/Io/IniUtil.php 0000664 0000000 0000000 00000002133 13263421677 0015737 0 ustar 00root root 0000000 0000000 stream = $stream;
$this->maxLength = $maxLength;
$this->stream->on('data', array($this, 'handleData'));
$this->stream->on('end', array($this, 'handleEnd'));
$this->stream->on('error', array($this, 'handleError'));
$this->stream->on('close', array($this, 'close'));
}
public function isReadable()
{
return !$this->closed && $this->stream->isReadable();
}
public function pause()
{
$this->stream->pause();
}
public function resume()
{
$this->stream->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->stream->close();
$this->emit('close');
$this->removeAllListeners();
}
/** @internal */
public function handleData($data)
{
if (($this->transferredLength + strlen($data)) > $this->maxLength) {
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
$data = (string)substr($data, 0, $this->maxLength - $this->transferredLength);
}
if ($data !== '') {
$this->transferredLength += strlen($data);
$this->emit('data', array($data));
}
if ($this->transferredLength === $this->maxLength) {
// 'Content-Length' reached, stream will end
$this->emit('end');
$this->close();
$this->stream->removeListener('data', array($this, 'handleData'));
}
}
/** @internal */
public function handleError(\Exception $e)
{
$this->emit('error', array($e));
$this->close();
}
/** @internal */
public function handleEnd()
{
if (!$this->closed) {
$this->handleError(new \Exception('Unexpected end event'));
}
}
}
http-0.8.3/src/Io/MiddlewareRunner.php 0000664 0000000 0000000 00000003124 13263421677 0017632 0 ustar 00root root 0000000 0000000 middleware = array_values($middleware);
}
/**
* @param ServerRequestInterface $request
* @return ResponseInterface|PromiseInterface
* @throws \Exception
*/
public function __invoke(ServerRequestInterface $request)
{
if (empty($this->middleware)) {
throw new \RuntimeException('No middleware to run');
}
return $this->call($request, 0);
}
/** @internal */
public function call(ServerRequestInterface $request, $position)
{
// final request handler will be invoked without a next handler
if (!isset($this->middleware[$position + 1])) {
$handler = $this->middleware[$position];
return $handler($request);
}
$that = $this;
$next = function (ServerRequestInterface $request) use ($that, $position) {
return $that->call($request, $position + 1);
};
// invoke middleware request handler with next handler
$handler = $this->middleware[$position];
return $handler($request, $next);
}
}
http-0.8.3/src/Io/MultipartParser.php 0000664 0000000 0000000 00000022027 13263421677 0017524 0 ustar 00root root 0000000 0000000 maxInputVars = (int)$var;
}
$var = ini_get('max_input_nesting_level');
if ($var !== false) {
$this->maxInputNestingLevel = (int)$var;
}
if ($uploadMaxFilesize === null) {
$uploadMaxFilesize = ini_get('upload_max_filesize');
}
$this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
$this->maxFileUploads = $maxFileUploads === null ? (ini_get('file_uploads') === '' ? 0 : (int)ini_get('max_file_uploads')) : (int)$maxFileUploads;
}
public function parse(ServerRequestInterface $request)
{
$contentType = $request->getHeaderLine('content-type');
if(!preg_match('/boundary="?(.*)"?$/', $contentType, $matches)) {
return $request;
}
$this->request = $request;
$this->parseBody('--' . $matches[1], (string)$request->getBody());
$request = $this->request;
$this->request = null;
$this->postCount = 0;
$this->filesCount = 0;
$this->emptyCount = 0;
$this->maxFileSize = null;
return $request;
}
private function parseBody($boundary, $buffer)
{
$len = strlen($boundary);
// ignore everything before initial boundary (SHOULD be empty)
$start = strpos($buffer, $boundary . "\r\n");
while ($start !== false) {
// search following boundary (preceded by newline)
// ignore last if not followed by boundary (SHOULD end with "--")
$start += $len + 2;
$end = strpos($buffer, "\r\n" . $boundary, $start);
if ($end === false) {
break;
}
// parse one part and continue searching for next
$this->parsePart(substr($buffer, $start, $end - $start));
$start = $end;
}
}
private function parsePart($chunk)
{
$pos = strpos($chunk, "\r\n\r\n");
if ($pos === false) {
return;
}
$headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
$body = (string)substr($chunk, $pos + 4);
if (!isset($headers['content-disposition'])) {
return;
}
$name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
if ($name === null) {
return;
}
$filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
if ($filename !== null) {
$this->parseFile(
$name,
$filename,
isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
$body
);
} else {
$this->parsePost($name, $body);
}
}
private function parseFile($name, $filename, $contentType, $contents)
{
$file = $this->parseUploadedFile($filename, $contentType, $contents);
if ($file === null) {
return;
}
$this->request = $this->request->withUploadedFiles($this->extractPost(
$this->request->getUploadedFiles(),
$name,
$file
));
}
private function parseUploadedFile($filename, $contentType, $contents)
{
$size = strlen($contents);
// no file selected (zero size and empty filename)
if ($size === 0 && $filename === '') {
// ignore excessive number of empty file uploads
if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
return;
}
return new UploadedFile(
Psr7\stream_for(),
$size,
UPLOAD_ERR_NO_FILE,
$filename,
$contentType
);
}
// ignore excessive number of file uploads
if (++$this->filesCount > $this->maxFileUploads) {
return;
}
// file exceeds "upload_max_filesize" ini setting
if ($size > $this->uploadMaxFilesize) {
return new UploadedFile(
Psr7\stream_for(),
$size,
UPLOAD_ERR_INI_SIZE,
$filename,
$contentType
);
}
// file exceeds MAX_FILE_SIZE value
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
return new UploadedFile(
Psr7\stream_for(),
$size,
UPLOAD_ERR_FORM_SIZE,
$filename,
$contentType
);
}
return new UploadedFile(
Psr7\stream_for($contents),
$size,
UPLOAD_ERR_OK,
$filename,
$contentType
);
}
private function parsePost($name, $value)
{
// ignore excessive number of post fields
if (++$this->postCount > $this->maxInputVars) {
return;
}
$this->request = $this->request->withParsedBody($this->extractPost(
$this->request->getParsedBody(),
$name,
$value
));
if (strtoupper($name) === 'MAX_FILE_SIZE') {
$this->maxFileSize = (int)$value;
if ($this->maxFileSize === 0) {
$this->maxFileSize = null;
}
}
}
private function parseHeaders($header)
{
$headers = array();
foreach (explode("\r\n", trim($header)) as $line) {
$parts = explode(':', $line, 2);
if (!isset($parts[1])) {
continue;
}
$key = strtolower(trim($parts[0]));
$values = explode(';', $parts[1]);
$values = array_map('trim', $values);
$headers[$key] = $values;
}
return $headers;
}
private function getParameterFromHeader(array $header, $parameter)
{
foreach ($header as $part) {
if (preg_match('/' . $parameter . '="?(.*)"$/', $part, $matches)) {
return $matches[1];
}
}
return null;
}
private function extractPost($postFields, $key, $value)
{
$chunks = explode('[', $key);
if (count($chunks) == 1) {
$postFields[$key] = $value;
return $postFields;
}
// ignore this key if maximum nesting level is exceeded
if (isset($chunks[$this->maxInputNestingLevel])) {
return $postFields;
}
$chunkKey = rtrim($chunks[0], ']');
$parent = &$postFields;
for ($i = 1; isset($chunks[$i]); $i++) {
$previousChunkKey = $chunkKey;
if ($previousChunkKey === '') {
$parent[] = array();
end($parent);
$parent = &$parent[key($parent)];
} else {
if (!isset($parent[$previousChunkKey]) || !is_array($parent[$previousChunkKey])) {
$parent[$previousChunkKey] = array();
}
$parent = &$parent[$previousChunkKey];
}
$chunkKey = rtrim($chunks[$i], ']');
}
if ($chunkKey === '') {
$parent[] = $value;
} else {
$parent[$chunkKey] = $value;
}
return $postFields;
}
}
http-0.8.3/src/Io/PauseBufferStream.php 0000664 0000000 0000000 00000010306 13263421677 0017746 0 ustar 00root root 0000000 0000000 input = $input;
$this->input->on('data', array($this, 'handleData'));
$this->input->on('end', array($this, 'handleEnd'));
$this->input->on('error', array($this, 'handleError'));
$this->input->on('close', array($this, 'handleClose'));
}
/**
* pause and remember this was not explicitly from user control
*
* @internal
*/
public function pauseImplicit()
{
$this->pause();
$this->implicit = true;
}
/**
* resume only if this was previously paused implicitly and not explicitly from user control
*
* @internal
*/
public function resumeImplicit()
{
if ($this->implicit) {
$this->resume();
}
}
public function isReadable()
{
return !$this->closed;
}
public function pause()
{
if ($this->closed) {
return;
}
$this->input->pause();
$this->paused = true;
$this->implicit = false;
}
public function resume()
{
if ($this->closed) {
return;
}
$this->paused = false;
$this->implicit = false;
if ($this->dataPaused !== '') {
$this->emit('data', array($this->dataPaused));
$this->dataPaused = '';
}
if ($this->errorPaused) {
$this->emit('error', array($this->errorPaused));
return $this->close();
}
if ($this->endPaused) {
$this->endPaused = false;
$this->emit('end');
return $this->close();
}
if ($this->closePaused) {
$this->closePaused = false;
return $this->close();
}
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
public function close()
{
if ($this->closed) {
return;
}
$this->closed = true;
$this->dataPaused = '';
$this->endPaused = $this->closePaused = false;
$this->errorPaused = null;
$this->input->close();
$this->emit('close');
$this->removeAllListeners();
}
/** @internal */
public function handleData($data)
{
if ($this->paused) {
$this->dataPaused .= $data;
return;
}
$this->emit('data', array($data));
}
/** @internal */
public function handleError(\Exception $e)
{
if ($this->paused) {
$this->errorPaused = $e;
return;
}
$this->emit('error', array($e));
$this->close();
}
/** @internal */
public function handleEnd()
{
if ($this->paused) {
$this->endPaused = true;
return;
}
if (!$this->closed) {
$this->emit('end');
$this->close();
}
}
/** @internal */
public function handleClose()
{
if ($this->paused) {
$this->closePaused = true;
return;
}
$this->close();
}
}
http-0.8.3/src/Io/RequestHeaderParser.php 0000664 0000000 0000000 00000021713 13263421677 0020305 0 ustar 00root root 0000000 0000000 localSocketUri = $localSocketUri;
$this->remoteSocketUri = $remoteSocketUri;
}
public function feed($data)
{
$this->buffer .= $data;
$endOfHeader = strpos($this->buffer, "\r\n\r\n");
if ($endOfHeader > $this->maxSize || ($endOfHeader === false && isset($this->buffer[$this->maxSize]))) {
$this->emit('error', array(new \OverflowException("Maximum header size of {$this->maxSize} exceeded.", 431), $this));
$this->removeAllListeners();
return;
}
if (false !== $endOfHeader) {
try {
$this->parseAndEmitRequest($endOfHeader);
} catch (Exception $exception) {
$this->emit('error', array($exception));
}
$this->removeAllListeners();
}
}
private function parseAndEmitRequest($endOfHeader)
{
$request = $this->parseRequest((string)substr($this->buffer, 0, $endOfHeader));
$bodyBuffer = isset($this->buffer[$endOfHeader + 4]) ? substr($this->buffer, $endOfHeader + 4) : '';
$this->emit('headers', array($request, $bodyBuffer));
}
private function parseRequest($headers)
{
// additional, stricter safe-guard for request line
// because request parser doesn't properly cope with invalid ones
if (!preg_match('#^[^ ]+ [^ ]+ HTTP/\d\.\d#m', $headers)) {
throw new \InvalidArgumentException('Unable to parse invalid request-line');
}
// parser does not support asterisk-form and authority-form
// remember original target and temporarily replace and re-apply below
$originalTarget = null;
if (strncmp($headers, 'OPTIONS * ', 10) === 0) {
$originalTarget = '*';
$headers = 'OPTIONS / ' . substr($headers, 10);
} elseif (strncmp($headers, 'CONNECT ', 8) === 0) {
$parts = explode(' ', $headers, 3);
$uri = parse_url('tcp://' . $parts[1]);
// check this is a valid authority-form request-target (host:port)
if (isset($uri['scheme'], $uri['host'], $uri['port']) && count($uri) === 3) {
$originalTarget = $parts[1];
$parts[1] = 'http://' . $parts[1] . '/';
$headers = implode(' ', $parts);
} else {
throw new \InvalidArgumentException('CONNECT method MUST use authority-form request target');
}
}
// parse request headers into obj implementing RequestInterface
$request = g7\parse_request($headers);
// create new obj implementing ServerRequestInterface by preserving all
// previous properties and restoring original request-target
$serverParams = array(
'REQUEST_TIME' => time(),
'REQUEST_TIME_FLOAT' => microtime(true)
);
// apply REMOTE_ADDR and REMOTE_PORT if source address is known
// address should always be known, unless this is over Unix domain sockets (UDS)
if ($this->remoteSocketUri !== null) {
$remoteAddress = parse_url($this->remoteSocketUri);
$serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
$serverParams['REMOTE_PORT'] = $remoteAddress['port'];
}
// apply SERVER_ADDR and SERVER_PORT if server address is known
// address should always be known, even for Unix domain sockets (UDS)
// but skip UDS as it doesn't have a concept of host/port.s
if ($this->localSocketUri !== null) {
$localAddress = parse_url($this->localSocketUri);
if (isset($localAddress['host'], $localAddress['port'])) {
$serverParams['SERVER_ADDR'] = $localAddress['host'];
$serverParams['SERVER_PORT'] = $localAddress['port'];
}
if (isset($localAddress['scheme']) && $localAddress['scheme'] === 'https') {
$serverParams['HTTPS'] = 'on';
}
}
$target = $request->getRequestTarget();
$request = new ServerRequest(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
$request->getBody(),
$request->getProtocolVersion(),
$serverParams
);
$request = $request->withRequestTarget($target);
// Add query params
$queryString = $request->getUri()->getQuery();
if ($queryString !== '') {
$queryParams = array();
parse_str($queryString, $queryParams);
$request = $request->withQueryParams($queryParams);
}
$cookies = ServerRequest::parseCookie($request->getHeaderLine('Cookie'));
if ($cookies !== false) {
$request = $request->withCookieParams($cookies);
}
// re-apply actual request target from above
if ($originalTarget !== null) {
$request = $request->withUri(
$request->getUri()->withPath(''),
true
)->withRequestTarget($originalTarget);
}
// only support HTTP/1.1 and HTTP/1.0 requests
$protocolVersion = $request->getProtocolVersion();
if ($protocolVersion !== '1.1' && $protocolVersion !== '1.0') {
throw new \InvalidArgumentException('Received request with invalid protocol version', 505);
}
// ensure absolute-form request-target contains a valid URI
$requestTarget = $request->getRequestTarget();
if (strpos($requestTarget, '://') !== false && substr($requestTarget, 0, 1) !== '/') {
$parts = parse_url($requestTarget);
// make sure value contains valid host component (IP or hostname), but no fragment
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) {
throw new \InvalidArgumentException('Invalid absolute-form request-target');
}
}
// Optional Host header value MUST be valid (host and optional port)
if ($request->hasHeader('Host')) {
$parts = parse_url('http://' . $request->getHeaderLine('Host'));
// make sure value contains valid host component (IP or hostname)
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
$parts = false;
}
// make sure value does not contain any other URI component
unset($parts['scheme'], $parts['host'], $parts['port']);
if ($parts === false || $parts) {
throw new \InvalidArgumentException('Invalid Host header value');
}
}
// set URI components from socket address if not already filled via Host header
if ($request->getUri()->getHost() === '') {
$parts = parse_url($this->localSocketUri);
if (!isset($parts['host'], $parts['port'])) {
$parts = array('host' => '127.0.0.1', 'port' => 80);
}
$request = $request->withUri(
$request->getUri()->withScheme('http')->withHost($parts['host'])->withPort($parts['port']),
true
);
}
// Do not assume this is HTTPS when this happens to be port 443
// detecting HTTPS is left up to the socket layer (TLS detection)
if ($request->getUri()->getScheme() === 'https') {
$request = $request->withUri(
$request->getUri()->withScheme('http')->withPort(443),
true
);
}
// Update request URI to "https" scheme if the connection is encrypted
$parts = parse_url($this->localSocketUri);
if (isset($parts['scheme']) && $parts['scheme'] === 'https') {
// The request URI may omit default ports here, so try to parse port
// from Host header field (if possible)
$port = $request->getUri()->getPort();
if ($port === null) {
$port = parse_url('tcp://' . $request->getHeaderLine('Host'), PHP_URL_PORT); // @codeCoverageIgnore
}
$request = $request->withUri(
$request->getUri()->withScheme('https')->withPort($port),
true
);
}
// always sanitize Host header because it contains critical routing information
$request = $request->withUri($request->getUri()->withUserInfo('u')->withUserInfo(''));
return $request;
}
}
http-0.8.3/src/Io/ServerRequest.php 0000664 0000000 0000000 00000010150 13263421677 0017177 0 ustar 00root root 0000000 0000000 serverParams = $serverParams;
parent::__construct($method, $uri, $headers, $body, $protocolVersion);
}
public function getServerParams()
{
return $this->serverParams;
}
public function getCookieParams()
{
return $this->cookies;
}
public function withCookieParams(array $cookies)
{
$new = clone $this;
$new->cookies = $cookies;
return $new;
}
public function getQueryParams()
{
return $this->queryParams;
}
public function withQueryParams(array $query)
{
$new = clone $this;
$new->queryParams = $query;
return $new;
}
public function getUploadedFiles()
{
return $this->fileParams;
}
public function withUploadedFiles(array $uploadedFiles)
{
$new = clone $this;
$new->fileParams = $uploadedFiles;
return $new;
}
public function getParsedBody()
{
return $this->parsedBody;
}
public function withParsedBody($data)
{
$new = clone $this;
$new->parsedBody = $data;
return $new;
}
public function getAttributes()
{
return $this->attributes;
}
public function getAttribute($name, $default = null)
{
if (!array_key_exists($name, $this->attributes)) {
return $default;
}
return $this->attributes[$name];
}
public function withAttribute($name, $value)
{
$new = clone $this;
$new->attributes[$name] = $value;
return $new;
}
public function withoutAttribute($name)
{
$new = clone $this;
unset($new->attributes[$name]);
return $new;
}
/**
* @internal
* @param string $cookie
* @return boolean|mixed[]
*/
public static function parseCookie($cookie)
{
// PSR-7 `getHeaderLine('Cookies')` will return multiple
// cookie header comma-seperated. Multiple cookie headers
// are not allowed according to https://tools.ietf.org/html/rfc6265#section-5.4
if (strpos($cookie, ',') !== false) {
return false;
}
$cookieArray = explode(';', $cookie);
$result = array();
foreach ($cookieArray as $pair) {
$pair = trim($pair);
$nameValuePair = explode('=', $pair, 2);
if (count($nameValuePair) === 2) {
$key = urldecode($nameValuePair[0]);
$value = urldecode($nameValuePair[1]);
$result[$key] = $value;
}
}
return $result;
}
}
http-0.8.3/src/Io/UploadedFile.php 0000664 0000000 0000000 00000005113 13263421677 0016720 0 ustar 00root root 0000000 0000000 stream = $stream;
$this->size = $size;
if (!is_int($error) || !in_array($error, array(
UPLOAD_ERR_OK,
UPLOAD_ERR_INI_SIZE,
UPLOAD_ERR_FORM_SIZE,
UPLOAD_ERR_PARTIAL,
UPLOAD_ERR_NO_FILE,
UPLOAD_ERR_NO_TMP_DIR,
UPLOAD_ERR_CANT_WRITE,
UPLOAD_ERR_EXTENSION,
))) {
throw new InvalidArgumentException(
'Invalid error code, must be an UPLOAD_ERR_* constant'
);
}
$this->error = $error;
$this->filename = $filename;
$this->mediaType = $mediaType;
}
/**
* {@inheritdoc}
*/
public function getStream()
{
if ($this->error !== UPLOAD_ERR_OK) {
throw new RuntimeException('Cannot retrieve stream due to upload error');
}
return $this->stream;
}
/**
* {@inheritdoc}
*/
public function moveTo($targetPath)
{
throw new RuntimeException('Not implemented');
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return $this->size;
}
/**
* {@inheritdoc}
*/
public function getError()
{
return $this->error;
}
/**
* {@inheritdoc}
*/
public function getClientFilename()
{
return $this->filename;
}
/**
* {@inheritdoc}
*/
public function getClientMediaType()
{
return $this->mediaType;
}
}
http-0.8.3/src/Middleware/ 0000775 0000000 0000000 00000000000 13263421677 0015360 5 ustar 00root root 0000000 0000000 http-0.8.3/src/Middleware/LimitConcurrentRequestsMiddleware.php 0000664 0000000 0000000 00000015720 13263421677 0024751 0 ustar 00root root 0000000 0000000 limit = $limit;
}
public function __invoke(ServerRequestInterface $request, $next)
{
// happy path: simply invoke next request handler if we're below limit
if ($this->pending < $this->limit) {
++$this->pending;
try {
$response = $next($request);
} catch (\Exception $e) {
$this->processQueue();
throw $e;
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
// handle Errors just like Exceptions (PHP 7+ only)
$this->processQueue();
throw $e; // @codeCoverageIgnoreEnd
}
// happy path: if next request handler returned immediately,
// we can simply try to invoke the next queued request
if ($response instanceof ResponseInterface) {
$this->processQueue();
return $response;
}
// if the next handler returns a pending promise, we have to
// await its resolution before invoking next queued request
return $this->await(Promise\resolve($response));
}
// if we reach this point, then this request will need to be queued
// check if the body is streaming, in which case we need to buffer everything
$body = $request->getBody();
if ($body instanceof ReadableStreamInterface) {
// pause actual body to stop emitting data until the handler is called
$size = $body->getSize();
$body = new PauseBufferStream($body);
$body->pauseImplicit();
// replace with buffering body to ensure any readable events will be buffered
$request = $request->withBody(new HttpBodyStream(
$body,
$size
));
}
// get next queue position
$queue =& $this->queue;
$queue[] = null;
end($queue);
$id = key($queue);
$deferred = new Deferred(function ($_, $reject) use (&$queue, $id) {
// queued promise cancelled before its next handler is invoked
// remove from queue and reject explicitly
unset($queue[$id]);
$reject(new \RuntimeException('Cancelled queued next handler'));
});
// queue request and process queue if pending does not exceed limit
$queue[$id] = $deferred;
$pending = &$this->pending;
$that = $this;
return $deferred->promise()->then(function () use ($request, $next, $body, &$pending, $that) {
// invoke next request handler
++$pending;
try {
$response = $next($request);
} catch (\Exception $e) {
$that->processQueue();
throw $e;
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
// handle Errors just like Exceptions (PHP 7+ only)
$that->processQueue();
throw $e; // @codeCoverageIgnoreEnd
}
// resume readable stream and replay buffered events
if ($body instanceof PauseBufferStream) {
$body->resumeImplicit();
}
// if the next handler returns a pending promise, we have to
// await its resolution before invoking next queued request
return $that->await(Promise\resolve($response));
});
}
/**
* @internal
* @param PromiseInterface $promise
* @return PromiseInterface
*/
public function await(PromiseInterface $promise)
{
$that = $this;
return $promise->then(function ($response) use ($that) {
$that->processQueue();
return $response;
}, function ($error) use ($that) {
$that->processQueue();
return Promise\reject($error);
});
}
/**
* @internal
*/
public function processQueue()
{
// skip if we're still above concurrency limit or there's no queued request waiting
if (--$this->pending >= $this->limit || !$this->queue) {
return;
}
$first = reset($this->queue);
unset($this->queue[key($this->queue)]);
$first->resolve();
}
}
http-0.8.3/src/Middleware/RequestBodyBufferMiddleware.php 0000664 0000000 0000000 00000005001 13263421677 0023463 0 ustar 00root root 0000000 0000000 sizeLimit = IniUtil::iniSizeToBytes($sizeLimit);
}
public function __invoke(ServerRequestInterface $request, $stack)
{
$body = $request->getBody();
$size = $body->getSize();
// happy path: skip if body is known to be empty (or is already buffered)
if ($size === 0 || !$body instanceof ReadableStreamInterface) {
// replace with empty body if body is streaming (or buffered size exceeds limit)
if ($body instanceof ReadableStreamInterface || $size > $this->sizeLimit) {
$request = $request->withBody(new BufferStream(0));
}
return $stack($request);
}
// request body of known size exceeding limit
$sizeLimit = $this->sizeLimit;
if ($size > $this->sizeLimit) {
$sizeLimit = 0;
}
return Stream\buffer($body, $sizeLimit)->then(function ($buffer) use ($request, $stack) {
$stream = new BufferStream(strlen($buffer));
$stream->write($buffer);
$request = $request->withBody($stream);
return $stack($request);
}, function ($error) use ($stack, $request, $body) {
// On buffer overflow keep the request body stream in,
// but ignore the contents and wait for the close event
// before passing the request on to the next middleware.
if ($error instanceof OverflowException) {
return Stream\first($body, 'close')->then(function () use ($stack, $request) {
return $stack($request);
});
}
throw $error;
});
}
}
http-0.8.3/src/Middleware/RequestBodyParserMiddleware.php 0000664 0000000 0000000 00000002452 13263421677 0023515 0 ustar 00root root 0000000 0000000 multipart = new MultipartParser($uploadMaxFilesize, $maxFileUploads);
}
public function __invoke(ServerRequestInterface $request, $next)
{
$type = strtolower($request->getHeaderLine('Content-Type'));
list ($type) = explode(';', $type);
if ($type === 'application/x-www-form-urlencoded') {
return $next($this->parseFormUrlencoded($request));
}
if ($type === 'multipart/form-data') {
return $next($this->multipart->parse($request));
}
return $next($request);
}
private function parseFormUrlencoded(ServerRequestInterface $request)
{
// parse string into array structure
// ignore warnings due to excessive data structures (max_input_vars and max_input_nesting_level)
$ret = array();
@parse_str((string)$request->getBody(), $ret);
return $request->withParsedBody($ret);
}
}
http-0.8.3/src/Response.php 0000664 0000000 0000000 00000001552 13263421677 0015615 0 ustar 00root root 0000000 0000000 getConcurrentRequestsLimit());
$middleware[] = new RequestBodyBufferMiddleware();
// Checking for an empty string because that is what a boolean
// false is returned as by ini_get depending on the PHP version.
// @link http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading
// @link http://php.net/manual/en/function.ini-get.php#refsect1-function.ini-get-notes
// @link https://3v4l.org/qJtsa
$enablePostDataReading = ini_get('enable_post_data_reading');
if ($enablePostDataReading !== '') {
$middleware[] = new RequestBodyParserMiddleware();
}
if (is_callable($requestHandler)) {
$middleware[] = $requestHandler;
} else {
$middleware = array_merge($middleware, $requestHandler);
}
$this->streamingServer = new StreamingServer($middleware);
$that = $this;
$this->streamingServer->on('error', function ($error) use ($that) {
$that->emit('error', array($error));
});
}
/**
* @see StreamingServer::listen()
*/
public function listen(ServerInterface $server)
{
$this->streamingServer->listen($server);
}
/**
* @return int
* @codeCoverageIgnore
*/
private function getConcurrentRequestsLimit()
{
if (ini_get('memory_limit') == -1) {
return self::MAXIMUM_CONCURRENT_REQUESTS;
}
$availableMemory = IniUtil::iniSizeToBytes(ini_get('memory_limit')) / 4;
$concurrentRequests = ceil($availableMemory / IniUtil::iniSizeToBytes(ini_get('post_max_size')));
if ($concurrentRequests >= self::MAXIMUM_CONCURRENT_REQUESTS) {
return self::MAXIMUM_CONCURRENT_REQUESTS;
}
return $concurrentRequests;
}
}
http-0.8.3/src/StreamingServer.php 0000664 0000000 0000000 00000043660 13263421677 0017145 0 ustar 00root root 0000000 0000000 'text/plain'),
* "Hello World!\n"
* );
* });
* ```
*
* In order to process any connections, the server needs to be attached to an
* instance of `React\Socket\ServerInterface` which emits underlying streaming
* connections in order to then parse incoming data as HTTP.
*
* ```php
* $socket = new React\Socket\Server(8080, $loop);
* $server->listen($socket);
* ```
*
* See also the [listen()](#listen) method and the [first example](examples) for more details.
*
* When HTTP/1.1 clients want to send a bigger request body, they MAY send only
* the request headers with an additional `Expect: 100-continue` header and
* wait before sending the actual (large) message body.
* In this case the server will automatically send an intermediary
* `HTTP/1.1 100 Continue` response to the client.
* This ensures you will receive the request body without a delay as expected.
* The [Response](#response) still needs to be created as described in the
* examples above.
*
* See also [request](#request) and [response](#response)
* for more details (e.g. the request data body).
*
* The `Server` supports both HTTP/1.1 and HTTP/1.0 request messages.
* If a client sends an invalid request message, uses an invalid HTTP protocol
* version or sends an invalid `Transfer-Encoding` in the request header, it will
* emit an `error` event, send an HTTP error response to the client and
* close the connection:
*
* ```php
* $server->on('error', function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* Note that the request object can also emit an error.
* Check out [request](#request) for more details.
*
* @see Request
* @see Response
* @see self::listen()
*/
final class StreamingServer extends EventEmitter
{
private $callback;
/**
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
*
* In order to process any connections, the server needs to be attached to an
* instance of `React\Socket\ServerInterface` which emits underlying streaming
* connections in order to then parse incoming data as HTTP.
* See also [listen()](#listen) for more details.
*
* @param callable|callable[] $requestHandler
* @see self::listen()
*/
public function __construct($requestHandler)
{
if (!is_callable($requestHandler) && !is_array($requestHandler)) {
throw new \InvalidArgumentException('Invalid request handler given');
} elseif (!is_callable($requestHandler)) {
$requestHandler = new MiddlewareRunner($requestHandler);
}
$this->callback = $requestHandler;
}
/**
* Starts listening for HTTP requests on the given socket server instance
*
* The server needs to be attached to an instance of
* `React\Socket\ServerInterface` which emits underlying streaming
* connections in order to then parse incoming data as HTTP.
* For each request, it executes the callback function passed to the
* constructor with the respective [request](#request) object and expects
* a respective [response](#response) object in return.
*
* You can attach this to a
* [`React\Socket\Server`](https://github.com/reactphp/socket#server)
* in order to start a plaintext HTTP server like this:
*
* ```php
* $server = new StreamingServer($handler);
*
* $socket = new React\Socket\Server(8080, $loop);
* $server->listen($socket);
* ```
*
* See also [example #1](examples) for more details.
*
* Similarly, you can also attach this to a
* [`React\Socket\SecureServer`](https://github.com/reactphp/socket#secureserver)
* in order to start a secure HTTPS server like this:
*
* ```php
* $server = new StreamingServer($handler);
*
* $socket = new React\Socket\Server(8080, $loop);
* $socket = new React\Socket\SecureServer($socket, $loop, array(
* 'local_cert' => __DIR__ . '/localhost.pem'
* ));
*
* $server->listen($socket);
* ```
*
* See also [example #11](examples) for more details.
*
* @param ServerInterface $socket
*/
public function listen(ServerInterface $socket)
{
$socket->on('connection', array($this, 'handleConnection'));
}
/** @internal */
public function handleConnection(ConnectionInterface $conn)
{
$uriLocal = $conn->getLocalAddress();
if ($uriLocal !== null) {
// local URI known, so translate transport scheme to application scheme
$uriLocal = strtr($uriLocal, array('tcp://' => 'http://', 'tls://' => 'https://'));
}
$uriRemote = $conn->getRemoteAddress();
$that = $this;
$parser = new RequestHeaderParser($uriLocal, $uriRemote);
$listener = array($parser, 'feed');
$parser->on('headers', function (ServerRequestInterface $request, $bodyBuffer) use ($conn, $listener, $that) {
// parsing request completed => stop feeding parser
$conn->removeListener('data', $listener);
$that->handleRequest($conn, $request);
if ($bodyBuffer !== '') {
$conn->emit('data', array($bodyBuffer));
}
});
$conn->on('data', $listener);
$parser->on('error', function(\Exception $e) use ($conn, $listener, $that) {
$conn->removeListener('data', $listener);
$that->emit('error', array($e));
$that->writeError(
$conn,
$e->getCode() !== 0 ? $e->getCode() : 400
);
});
}
/** @internal */
public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $request)
{
$contentLength = 0;
$stream = new CloseProtectionStream($conn);
if ($request->hasHeader('Transfer-Encoding')) {
if (strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
$this->emit('error', array(new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding')));
return $this->writeError($conn, 501, $request);
}
// Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
// as per https://tools.ietf.org/html/rfc7230#section-3.3.3
if ($request->hasHeader('Content-Length')) {
$this->emit('error', array(new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed')));
return $this->writeError($conn, 400, $request);
}
$stream = new ChunkedDecoder($stream);
$contentLength = null;
} elseif ($request->hasHeader('Content-Length')) {
$string = $request->getHeaderLine('Content-Length');
$contentLength = (int)$string;
if ((string)$contentLength !== $string) {
// Content-Length value is not an integer or not a single integer
$this->emit('error', array(new \InvalidArgumentException('The value of `Content-Length` is not valid')));
return $this->writeError($conn, 400, $request);
}
$stream = new LengthLimitedStream($stream, $contentLength);
}
$request = $request->withBody(new HttpBodyStream($stream, $contentLength));
if ($request->getProtocolVersion() !== '1.0' && '100-continue' === strtolower($request->getHeaderLine('Expect'))) {
$conn->write("HTTP/1.1 100 Continue\r\n\r\n");
}
// execute request handler callback
$callback = $this->callback;
try {
$response = $callback($request);
} catch (\Exception $error) {
// request handler callback throws an Exception
$response = Promise\reject($error);
} catch (\Throwable $error) { // @codeCoverageIgnoreStart
// request handler callback throws a PHP7+ Error
$response = Promise\reject($error); // @codeCoverageIgnoreEnd
}
// cancel pending promise once connection closes
if ($response instanceof CancellablePromiseInterface) {
$conn->on('close', function () use ($response) {
$response->cancel();
});
}
// happy path: request body is known to be empty => immediately end stream
if ($contentLength === 0) {
$stream->emit('end');
$stream->close();
}
// happy path: response returned, handle and return immediately
if ($response instanceof ResponseInterface) {
return $this->handleResponse($conn, $request, $response);
}
// did not return a promise? this is an error, convert into one for rejection below.
if (!$response instanceof PromiseInterface) {
$response = Promise\resolve($response);
}
$that = $this;
$response->then(
function ($response) use ($that, $conn, $request) {
if (!$response instanceof ResponseInterface) {
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but resolved with "%s" instead.';
$message = sprintf($message, is_object($response) ? get_class($response) : gettype($response));
$exception = new \RuntimeException($message);
$that->emit('error', array($exception));
return $that->writeError($conn, 500, $request);
}
$that->handleResponse($conn, $request, $response);
},
function ($error) use ($that, $conn, $request) {
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but rejected with "%s" instead.';
$message = sprintf($message, is_object($error) ? get_class($error) : gettype($error));
$previous = null;
if ($error instanceof \Throwable || $error instanceof \Exception) {
$previous = $error;
}
$exception = new \RuntimeException($message, null, $previous);
$that->emit('error', array($exception));
return $that->writeError($conn, 500, $request);
}
);
}
/** @internal */
public function writeError(ConnectionInterface $conn, $code, ServerRequestInterface $request = null)
{
$response = new Response(
$code,
array(
'Content-Type' => 'text/plain'
),
'Error ' . $code
);
// append reason phrase to response body if known
$reason = $response->getReasonPhrase();
if ($reason !== '') {
$body = $response->getBody();
$body->seek(0, SEEK_END);
$body->write(': ' . $reason);
}
if ($request === null) {
$request = new ServerRequest('GET', '/', array(), null, '1.1');
}
$this->handleResponse($conn, $request, $response);
}
/** @internal */
public function handleResponse(ConnectionInterface $connection, ServerRequestInterface $request, ResponseInterface $response)
{
// return early and close response body if connection is already closed
$body = $response->getBody();
if (!$connection->isWritable()) {
$body->close();
return;
}
$response = $response->withProtocolVersion($request->getProtocolVersion());
// assign default "X-Powered-By" header as first for history reasons
if (!$response->hasHeader('X-Powered-By')) {
$response = $response->withHeader('X-Powered-By', 'React/alpha');
}
if ($response->hasHeader('X-Powered-By') && $response->getHeaderLine('X-Powered-By') === ''){
$response = $response->withoutHeader('X-Powered-By');
}
$response = $response->withoutHeader('Transfer-Encoding');
// assign date header if no 'date' is given, use the current time where this code is running
if (!$response->hasHeader('Date')) {
// IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
$response = $response->withHeader('Date', gmdate('D, d M Y H:i:s') . ' GMT');
}
if ($response->hasHeader('Date') && $response->getHeaderLine('Date') === ''){
$response = $response->withoutHeader('Date');
}
if (!$body instanceof HttpBodyStream) {
$response = $response->withHeader('Content-Length', (string)$body->getSize());
} elseif (!$response->hasHeader('Content-Length') && $request->getProtocolVersion() === '1.1') {
// assign chunked transfer-encoding if no 'content-length' is given for HTTP/1.1 responses
$response = $response->withHeader('Transfer-Encoding', 'chunked');
}
// HTTP/1.1 assumes persistent connection support by default
// we do not support persistent connections, so let the client know
if ($request->getProtocolVersion() === '1.1') {
$response = $response->withHeader('Connection', 'close');
}
// 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header
$code = $response->getStatusCode();
if (($request->getMethod() === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === 204) {
$response = $response->withoutHeader('Content-Length')->withoutHeader('Transfer-Encoding');
}
// 101 (Switching Protocols) response uses Connection: upgrade header
// persistent connections are currently not supported, so do not use
// this for any other replies in order to preserve "Connection: close"
if ($code === 101) {
$response = $response->withHeader('Connection', 'upgrade');
}
// 101 (Switching Protocols) response (for Upgrade request) forwards upgraded data through duplex stream
// 2xx (Successful) response to CONNECT forwards tunneled application data through duplex stream
if (($code === 101 || ($request->getMethod() === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) {
if ($request->getBody()->isReadable()) {
// request is still streaming => wait for request close before forwarding following data from connection
$request->getBody()->on('close', function () use ($connection, $body) {
if ($body->input->isWritable()) {
$connection->pipe($body->input);
$connection->resume();
}
});
} elseif ($body->input->isWritable()) {
// request already closed => forward following data from connection
$connection->pipe($body->input);
$connection->resume();
}
}
// build HTTP response header by appending status line and header fields
$headers = "HTTP/" . $response->getProtocolVersion() . " " . $response->getStatusCode() . " " . $response->getReasonPhrase() . "\r\n";
foreach ($response->getHeaders() as $name => $values) {
foreach ($values as $value) {
$headers .= $name . ": " . $value . "\r\n";
}
}
// response to HEAD and 1xx, 204 and 304 responses MUST NOT include a body
// exclude status 101 (Switching Protocols) here for Upgrade request handling above
if ($request->getMethod() === 'HEAD' || $code === 100 || ($code > 101 && $code < 200) || $code === 204 || $code === 304) {
$body = '';
}
// this is a non-streaming response body or the body stream already closed?
if (!$body instanceof ReadableStreamInterface || !$body->isReadable()) {
// add final chunk if a streaming body is already closed and uses `Transfer-Encoding: chunked`
if ($body instanceof ReadableStreamInterface && $response->getHeaderLine('Transfer-Encoding') === 'chunked') {
$body = "0\r\n\r\n";
}
// end connection after writing response headers and body
$connection->write($headers . "\r\n" . $body);
$connection->end();
return;
}
$connection->write($headers . "\r\n");
if ($response->getHeaderLine('Transfer-Encoding') === 'chunked') {
$body = new ChunkedEncoder($body);
}
// Close response stream once connection closes.
// Note that this TCP/IP close detection may take some time,
// in particular this may only fire on a later read/write attempt.
$connection->on('close', array($body, 'close'));
$body->pipe($connection);
}
}
http-0.8.3/tests/ 0000775 0000000 0000000 00000000000 13263421677 0013656 5 ustar 00root root 0000000 0000000 http-0.8.3/tests/CallableStub.php 0000664 0000000 0000000 00000000146 13263421677 0016725 0 ustar 00root root 0000000 0000000 getUri());
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://' . noScheme($socket->getAddress()) . '/', $response);
$socket->close();
}
public function testPlainHttpOnRandomPortWithSingleRequestHandlerArray()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(array(
function () {
return new Response(404);
},
));
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 404 Not Found", $response);
$socket->close();
}
public function testPlainHttpOnRandomPortWithoutHostHeaderUsesSocketUri()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://' . noScheme($socket->getAddress()) . '/', $response);
$socket->close();
}
public function testPlainHttpOnRandomPortWithOtherHostHeaderTakesPrecedence()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: localhost:1000\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://localhost:1000/', $response);
$socket->close();
}
public function testSecureHttpsOnRandomPort()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$socket = new Socket(0, $loop);
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('https://' . noScheme($socket->getAddress()) . '/', $response);
$socket->close();
}
public function testSecureHttpsReturnsData()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(
200,
array(),
str_repeat('.', 33000)
);
});
$socket = new Socket(0, $loop);
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$server->listen($socket);
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains("\r\nContent-Length: 33000\r\n", $response);
$this->assertStringEndsWith("\r\n". str_repeat('.', 33000), $response);
$socket->close();
}
public function testSecureHttpsOnRandomPortWithoutHostHeaderUsesSocketUri()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$socket = new Socket(0, $loop);
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('https://' . noScheme($socket->getAddress()) . '/', $response);
$socket->close();
}
public function testPlainHttpOnStandardPortReturnsUriWithNoPort()
{
$loop = Factory::create();
try {
$socket = new Socket(80, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 80 failed (root and unused?)');
}
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://127.0.0.1/', $response);
$socket->close();
}
public function testPlainHttpOnStandardPortWithoutHostHeaderReturnsUriWithNoPort()
{
$loop = Factory::create();
try {
$socket = new Socket(80, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 80 failed (root and unused?)');
}
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://127.0.0.1/', $response);
$socket->close();
}
public function testSecureHttpsOnStandardPortReturnsUriWithNoPort()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
try {
$socket = new Socket(443, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 443 failed (root and unused?)');
}
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('https://127.0.0.1/', $response);
$socket->close();
}
public function testSecureHttpsOnStandardPortWithoutHostHeaderUsesSocketUri()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
try {
$socket = new Socket(443, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 443 failed (root and unused?)');
}
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('https://127.0.0.1/', $response);
$socket->close();
}
public function testPlainHttpOnHttpsStandardPortReturnsUriWithPort()
{
$loop = Factory::create();
try {
$socket = new Socket(443, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 443 failed (root and unused?)');
}
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri());
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('http://127.0.0.1:443/', $response);
$socket->close();
}
public function testSecureHttpsOnHttpStandardPortReturnsUriWithPort()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
try {
$socket = new Socket(80, $loop);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Listening on port 80 failed (root and unused?)');
}
$socket = new SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/../examples/localhost.pem'
));
$connector = new Connector($loop, array(
'tls' => array('verify_peer' => false)
));
$server = new StreamingServer(function (RequestInterface $request) {
return new Response(200, array(), (string)$request->getUri() . 'x' . $request->getHeaderLine('Host'));
});
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertContains("HTTP/1.0 200 OK", $response);
$this->assertContains('https://127.0.0.1:80/', $response);
$socket->close();
}
public function testClosedStreamFromRequestHandlerWillSendEmptyBody()
{
$loop = Factory::create();
$connector = new Connector($loop);
$stream = new ThroughStream();
$stream->close();
$server = new StreamingServer(function (RequestInterface $request) use ($stream) {
return new Response(200, array(), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.0 200 OK", $response);
$this->assertStringEndsWith("\r\n\r\n", $response);
$socket->close();
}
public function testRequestHandlerWillReceiveCloseEventIfConnectionClosesWhileSendingBody()
{
$loop = Factory::create();
$connector = new Connector($loop);
$once = $this->expectCallableOnce();
$server = new StreamingServer(function (RequestInterface $request) use ($once) {
$request->getBody()->on('close', $once);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) use ($loop) {
$conn->write("GET / HTTP/1.0\r\nContent-Length: 100\r\n\r\n");
$loop->addTimer(0.001, function() use ($conn) {
$conn->end();
});
});
Block\sleep(0.1, $loop);
$socket->close();
}
public function testStreamFromRequestHandlerWillBeClosedIfConnectionClosesWhileSendingRequestBody()
{
$loop = Factory::create();
$connector = new Connector($loop);
$stream = new ThroughStream();
$server = new StreamingServer(function (RequestInterface $request) use ($stream) {
return new Response(200, array(), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) use ($loop) {
$conn->write("GET / HTTP/1.0\r\nContent-Length: 100\r\n\r\n");
$loop->addTimer(0.001, function() use ($conn) {
$conn->end();
});
});
// stream will be closed within 0.1s
$ret = Block\await(Stream\first($stream, 'close'), $loop, 0.1);
$socket->close();
$this->assertNull($ret);
}
public function testStreamFromRequestHandlerWillBeClosedIfConnectionCloses()
{
$loop = Factory::create();
$connector = new Connector($loop);
$stream = new ThroughStream();
$server = new StreamingServer(function (RequestInterface $request) use ($stream) {
return new Response(200, array(), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) use ($loop) {
$conn->write("GET / HTTP/1.0\r\n\r\n");
$loop->addTimer(0.1, function () use ($conn) {
$conn->close();
});
});
// await response stream to be closed
$ret = Block\await(Stream\first($stream, 'close'), $loop, 1.0);
$socket->close();
$this->assertNull($ret);
}
public function testUpgradeWithThroughStreamReturnsDataAsGiven()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) use ($loop) {
$stream = new ThroughStream();
$loop->addTimer(0.1, function () use ($stream) {
$stream->end();
});
return new Response(101, array('Upgrade' => 'echo'), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("GET / HTTP/1.1\r\nHost: example.com:80\r\nUpgrade: echo\r\n\r\n");
$conn->once('data', function () use ($conn) {
$conn->write('hello');
$conn->write('world');
});
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.1 101 Switching Protocols\r\n", $response);
$this->assertStringEndsWith("\r\n\r\nhelloworld", $response);
$socket->close();
}
public function testUpgradeWithRequestBodyAndThroughStreamReturnsDataAsGiven()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) use ($loop) {
$stream = new ThroughStream();
$loop->addTimer(0.1, function () use ($stream) {
$stream->end();
});
return new Response(101, array('Upgrade' => 'echo'), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("POST / HTTP/1.1\r\nHost: example.com:80\r\nUpgrade: echo\r\nContent-Length: 3\r\n\r\n");
$conn->write('hoh');
$conn->once('data', function () use ($conn) {
$conn->write('hello');
$conn->write('world');
});
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.1 101 Switching Protocols\r\n", $response);
$this->assertStringEndsWith("\r\n\r\nhelloworld", $response);
$socket->close();
}
public function testConnectWithThroughStreamReturnsDataAsGiven()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) use ($loop) {
$stream = new ThroughStream();
$loop->addTimer(0.1, function () use ($stream) {
$stream->end();
});
return new Response(200, array(), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n");
$conn->once('data', function () use ($conn) {
$conn->write('hello');
$conn->write('world');
});
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
$this->assertStringEndsWith("\r\n\r\nhelloworld", $response);
$socket->close();
}
public function testConnectWithThroughStreamReturnedFromPromiseReturnsDataAsGiven()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) use ($loop) {
$stream = new ThroughStream();
$loop->addTimer(0.1, function () use ($stream) {
$stream->end();
});
return new Promise\Promise(function ($resolve) use ($loop, $stream) {
$loop->addTimer(0.001, function () use ($resolve, $stream) {
$resolve(new Response(200, array(), $stream));
});
});
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n");
$conn->once('data', function () use ($conn) {
$conn->write('hello');
$conn->write('world');
});
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
$this->assertStringEndsWith("\r\n\r\nhelloworld", $response);
$socket->close();
}
public function testConnectWithClosedThroughStreamReturnsNoData()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(function (RequestInterface $request) {
$stream = new ThroughStream();
$stream->close();
return new Response(200, array(), $stream);
});
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write("CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n");
$conn->once('data', function () use ($conn) {
$conn->write('hello');
$conn->write('world');
});
return Stream\buffer($conn);
});
$response = Block\await($result, $loop, 1.0);
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
$this->assertStringEndsWith("\r\n\r\n", $response);
$socket->close();
}
public function testLimitConcurrentRequestsMiddlewareRequestStreamPausing()
{
$loop = Factory::create();
$connector = new Connector($loop);
$server = new StreamingServer(array(
new LimitConcurrentRequestsMiddleware(5),
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
function (ServerRequestInterface $request, $next) use ($loop) {
return new Promise\Promise(function ($resolve) use ($request, $loop, $next) {
$loop->addTimer(0.1, function () use ($request, $resolve, $next) {
$resolve($next($request));
});
});
},
function (ServerRequestInterface $request) {
return new Response(200, array(), (string)strlen((string)$request->getBody()));
}
));
$socket = new Socket(0, $loop);
$server->listen($socket);
$result = array();
for ($i = 0; $i < 6; $i++) {
$result[] = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
$conn->write(
"GET / HTTP/1.0\r\nContent-Length: 1024\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n" .
str_repeat('a', 1024) .
"\r\n\r\n"
);
return Stream\buffer($conn);
});
}
$responses = Block\await(Promise\all($result), $loop, 1.0);
foreach ($responses as $response) {
$this->assertContains("HTTP/1.0 200 OK", $response, $response);
$this->assertTrue(substr($response, -4) == 1024, $response);
}
$socket->close();
}
}
function noScheme($uri)
{
$pos = strpos($uri, '://');
if ($pos !== false) {
$uri = substr($uri, $pos + 3);
}
return $uri;
}
http-0.8.3/tests/Io/ 0000775 0000000 0000000 00000000000 13263421677 0014225 5 ustar 00root root 0000000 0000000 http-0.8.3/tests/Io/ChunkedDecoderTest.php 0000664 0000000 0000000 00000042736 13263421677 0020461 0 ustar 00root root 0000000 0000000 input = new ThroughStream();
$this->parser = new ChunkedDecoder($this->input);
}
public function testSimpleChunk()
{
$this->parser->on('data', $this->expectCallableOnceWith('hello'));
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("5\r\nhello\r\n"));
}
public function testTwoChunks()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('hello', 'bla')));
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("5\r\nhello\r\n3\r\nbla\r\n"));
}
public function testEnd()
{
$this->parser->on('end', $this->expectCallableOnce());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("0\r\n\r\n"));
}
public function testParameterWithEnd()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('hello', 'bla')));
$this->parser->on('end', $this->expectCallableOnce());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("5\r\nhello\r\n3\r\nbla\r\n0\r\n\r\n"));
}
public function testInvalidChunk()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("bla\r\n"));
}
public function testNeverEnd()
{
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("0\r\n"));
}
public function testWrongChunkHex()
{
$this->parser->on('error', $this->expectCallableOnce());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->input->emit('data', array("2\r\na\r\n5\r\nhello\r\n"));
}
public function testSplittedChunk()
{
$this->parser->on('data', $this->expectCallableOnceWith('welt'));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4\r\n"));
$this->input->emit('data', array("welt\r\n"));
}
public function testSplittedHeader()
{
$this->parser->on('data', $this->expectCallableOnceWith('welt'));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());#
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4"));
$this->input->emit('data', array("\r\nwelt\r\n"));
}
public function testSplittedBoth()
{
$this->parser->on('data', $this->expectCallableOnceWith('welt'));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("welt\r\n"));
}
public function testCompletlySplitted()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('we', 'lt')));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("we"));
$this->input->emit('data', array("lt\r\n"));
}
public function testMixed()
{
$this->parser->on('data', $this->expectCallableConsecutive(3, array('we', 'lt', 'hello')));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("we"));
$this->input->emit('data', array("lt\r\n"));
$this->input->emit('data', array("5\r\nhello\r\n"));
}
public function testBigger()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('abcdeabcdeabcdea', 'hello')));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("1"));
$this->input->emit('data', array("0"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("abcdeabcdeabcdea\r\n"));
$this->input->emit('data', array("5\r\nhello\r\n"));
}
public function testOneUnfinished()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('bla', 'hello')));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("3\r\n"));
$this->input->emit('data', array("bla\r\n"));
$this->input->emit('data', array("5\r\nhello"));
}
public function testChunkIsBiggerThenExpected()
{
$this->parser->on('data', $this->expectCallableOnceWith('hello'));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("5\r\n"));
$this->input->emit('data', array("hello world\r\n"));
}
public function testHandleUnexpectedEnd()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('end');
}
public function testExtensionWillBeIgnored()
{
$this->parser->on('data', $this->expectCallableOnceWith('bla'));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("3;hello=world;foo=bar\r\nbla"));
}
public function testChunkHeaderIsTooBig()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$data = '';
for ($i = 0; $i < 1025; $i++) {
$data .= 'a';
}
$this->input->emit('data', array($data));
}
public function testChunkIsMaximumSize()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$data = '';
for ($i = 0; $i < 1024; $i++) {
$data .= 'a';
}
$data .= "\r\n";
$this->input->emit('data', array($data));
}
public function testLateCrlf()
{
$this->parser->on('data', $this->expectCallableOnceWith('late'));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("4\r\nlate"));
$this->input->emit('data', array("\r"));
$this->input->emit('data', array("\n"));
}
public function testNoCrlfInChunk()
{
$this->parser->on('data', $this->expectCallableOnceWith('no'));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("2\r\nno crlf"));
}
public function testNoCrlfInChunkSplitted()
{
$this->parser->on('data', $this->expectCallableOnceWith('no'));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("2\r\n"));
$this->input->emit('data', array("no"));
$this->input->emit('data', array("further"));
$this->input->emit('data', array("clrf"));
}
public function testEmitEmptyChunkBody()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("2\r\n"));
$this->input->emit('data', array(""));
$this->input->emit('data', array(""));
}
public function testEmitCrlfAsChunkBody()
{
$this->parser->on('data', $this->expectCallableOnceWith("\r\n"));
$this->parser->on('close', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->input->emit('data', array("2\r\n"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("\r\n"));
}
public function testNegativeHeader()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("-2\r\n"));
}
public function testHexDecimalInBodyIsPotentialThread()
{
$this->parser->on('data', $this->expectCallableOnce('test'));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("4\r\ntest5\r\nworld"));
}
public function testHexDecimalInBodyIsPotentialThreadSplitted()
{
$this->parser->on('data', $this->expectCallableOnce('test'));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->input->emit('data', array("4"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("test"));
$this->input->emit('data', array("5"));
$this->input->emit('data', array("\r\n"));
$this->input->emit('data', array("world"));
}
public function testEmitSingleCharacter()
{
$this->parser->on('data', $this->expectCallableConsecutive(4, array('t', 'e', 's', 't')));
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableOnce());
$this->parser->on('error', $this->expectCallableNever());
$array = str_split("4\r\ntest\r\n0\r\n\r\n");
foreach ($array as $character) {
$this->input->emit('data', array($character));
}
}
public function testHandleError()
{
$this->parser->on('error', $this->expectCallableOnce());
$this->parser->on('close', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->input->emit('error', array(new \RuntimeException()));
$this->assertFalse($this->parser->isReadable());
}
public function testPauseStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$parser = new ChunkedDecoder($input);
$parser->pause();
}
public function testResumeStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$parser = new ChunkedDecoder($input);
$parser->pause();
$parser->resume();
}
public function testPipeStream()
{
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = $this->parser->pipe($dest);
$this->assertSame($dest, $ret);
}
public function testHandleClose()
{
$this->parser->on('close', $this->expectCallableOnce());
$this->input->close();
$this->input->emit('end', array());
$this->assertFalse($this->parser->isReadable());
}
public function testOutputStreamCanCloseInputStream()
{
$input = new ThroughStream();
$input->on('close', $this->expectCallableOnce());
$stream = new ChunkedDecoder($input);
$stream->on('close', $this->expectCallableOnce());
$stream->close();
$this->assertFalse($input->isReadable());
}
public function testLeadingZerosWillBeIgnored()
{
$this->parser->on('data', $this->expectCallableConsecutive(2, array('hello', 'hello world')));
$this->parser->on('error', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("00005\r\nhello\r\n"));
$this->input->emit('data', array("0000b\r\nhello world\r\n"));
}
public function testLeadingZerosInEndChunkWillBeIgnored()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableOnce());
$this->parser->on('close', $this->expectCallableOnce());
$this->input->emit('data', array("0000\r\n\r\n"));
}
public function testLeadingZerosInInvalidChunk()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->input->emit('data', array("0000hello\r\n\r\n"));
}
public function testEmptyHeaderLeadsToError()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->input->emit('data', array("\r\n\r\n"));
}
public function testEmptyHeaderAndFilledBodyLeadsToError()
{
$this->parser->on('data', $this->expectCallableNever());
$this->parser->on('error', $this->expectCallableOnce());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableOnce());
$this->input->emit('data', array("\r\nhello\r\n"));
}
public function testUpperCaseHexWillBeHandled()
{
$this->parser->on('data', $this->expectCallableOnceWith('0123456790'));
$this->parser->on('error', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("A\r\n0123456790\r\n"));
}
public function testLowerCaseHexWillBeHandled()
{
$this->parser->on('data', $this->expectCallableOnceWith('0123456790'));
$this->parser->on('error', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("a\r\n0123456790\r\n"));
}
public function testMixedUpperAndLowerCaseHexValuesInHeaderWillBeHandled()
{
$data = str_repeat('1', (int)hexdec('AA'));
$this->parser->on('data', $this->expectCallableOnceWith($data));
$this->parser->on('error', $this->expectCallableNever());
$this->parser->on('end', $this->expectCallableNever());
$this->parser->on('close', $this->expectCallableNever());
$this->input->emit('data', array("aA\r\n" . $data . "\r\n"));
}
}
http-0.8.3/tests/Io/ChunkedEncoderTest.php 0000664 0000000 0000000 00000004462 13263421677 0020465 0 ustar 00root root 0000000 0000000 input = new ThroughStream();
$this->chunkedStream = new ChunkedEncoder($this->input);
}
public function testChunked()
{
$this->chunkedStream->on('data', $this->expectCallableOnce(array("5\r\nhello\r\n")));
$this->input->emit('data', array('hello'));
}
public function testEmptyString()
{
$this->chunkedStream->on('data', $this->expectCallableNever());
$this->input->emit('data', array(''));
}
public function testBiggerStringToCheckHexValue()
{
$this->chunkedStream->on('data', $this->expectCallableOnce(array("1a\r\nabcdefghijklmnopqrstuvwxyz\r\n")));
$this->input->emit('data', array('abcdefghijklmnopqrstuvwxyz'));
}
public function testHandleClose()
{
$this->chunkedStream->on('close', $this->expectCallableOnce());
$this->input->close();
$this->assertFalse($this->chunkedStream->isReadable());
}
public function testHandleError()
{
$this->chunkedStream->on('error', $this->expectCallableOnce());
$this->chunkedStream->on('close', $this->expectCallableOnce());
$this->input->emit('error', array(new \RuntimeException()));
$this->assertFalse($this->chunkedStream->isReadable());
}
public function testPauseStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$parser = new ChunkedEncoder($input);
$parser->pause();
}
public function testResumeStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$parser = new ChunkedEncoder($input);
$parser->pause();
$parser->resume();
}
public function testPipeStream()
{
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = $this->chunkedStream->pipe($dest);
$this->assertSame($dest, $ret);
}
}
http-0.8.3/tests/Io/CloseProtectionStreamTest.php 0000664 0000000 0000000 00000011774 13263421677 0022100 0 ustar 00root root 0000000 0000000 getMockBuilder('React\Stream\ReadableStreamInterface')->disableOriginalConstructor()->getMock();
$input->expects($this->never())->method('pause');
$input->expects($this->never())->method('resume');
$input->expects($this->never())->method('close');
$protection = new CloseProtectionStream($input);
$protection->close();
}
public function testErrorWontCloseStream()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$protection->on('error', $this->expectCallableOnce());
$protection->on('close', $this->expectCallableNever());
$input->emit('error', array(new \RuntimeException()));
$this->assertTrue($protection->isReadable());
$this->assertTrue($input->isReadable());
}
public function testResumeStreamWillResumeInputStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$input->expects($this->once())->method('resume');
$protection = new CloseProtectionStream($input);
$protection->pause();
$protection->resume();
}
public function testCloseResumesInputStreamIfItWasPreviouslyPaused()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$input->expects($this->once())->method('resume');
$protection = new CloseProtectionStream($input);
$protection->pause();
$protection->close();
}
public function testInputStreamIsNotReadableAfterClose()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$protection->on('close', $this->expectCallableOnce());
$input->close();
$this->assertFalse($protection->isReadable());
$this->assertFalse($input->isReadable());
}
public function testPipeStream()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = $protection->pipe($dest);
$this->assertSame($dest, $ret);
}
public function testStopEmittingDataAfterClose()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$protection->on('data', $this->expectCallableNever());
$protection->on('close', $this->expectCallableOnce());
$protection->close();
$input->emit('data', array('hello'));
$this->assertFalse($protection->isReadable());
$this->assertTrue($input->isReadable());
}
public function testErrorIsNeverCalledAfterClose()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$protection->on('data', $this->expectCallableNever());
$protection->on('error', $this->expectCallableNever());
$protection->on('close', $this->expectCallableOnce());
$protection->close();
$input->emit('error', array(new \Exception()));
$this->assertFalse($protection->isReadable());
$this->assertTrue($input->isReadable());
}
public function testEndWontBeEmittedAfterClose()
{
$input = new ThroughStream();
$protection = new CloseProtectionStream($input);
$protection->on('data', $this->expectCallableNever());
$protection->on('close', $this->expectCallableOnce());
$protection->close();
$input->emit('end', array());
$this->assertFalse($protection->isReadable());
$this->assertTrue($input->isReadable());
}
public function testPauseAfterCloseHasNoEffect()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->never())->method('pause');
$input->expects($this->never())->method('resume');
$protection = new CloseProtectionStream($input);
$protection->on('data', $this->expectCallableNever());
$protection->on('close', $this->expectCallableOnce());
$protection->close();
$protection->pause();
}
public function testResumeAfterCloseHasNoEffect()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->never())->method('pause');
$input->expects($this->never())->method('resume');
$protection = new CloseProtectionStream($input);
$protection->on('data', $this->expectCallableNever());
$protection->on('close', $this->expectCallableOnce());
$protection->close();
$protection->resume();
}
}
http-0.8.3/tests/Io/HttpBodyStreamTest.php 0000664 0000000 0000000 00000010266 13263421677 0020514 0 ustar 00root root 0000000 0000000 input = new ThroughStream();
$this->bodyStream = new HttpBodyStream($this->input, null);
}
public function testDataEmit()
{
$this->bodyStream->on('data', $this->expectCallableOnce(array("hello")));
$this->input->emit('data', array("hello"));
}
public function testPauseStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$bodyStream = new HttpBodyStream($input, null);
$bodyStream->pause();
}
public function testResumeStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('resume');
$bodyStream = new HttpBodyStream($input, null);
$bodyStream->resume();
}
public function testPipeStream()
{
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = $this->bodyStream->pipe($dest);
$this->assertSame($dest, $ret);
}
public function testHandleClose()
{
$this->bodyStream->on('close', $this->expectCallableOnce());
$this->input->close();
$this->input->emit('end', array());
$this->assertFalse($this->bodyStream->isReadable());
}
public function testStopDataEmittingAfterClose()
{
$bodyStream = new HttpBodyStream($this->input, null);
$bodyStream->on('close', $this->expectCallableOnce());
$this->bodyStream->on('data', $this->expectCallableOnce(array("hello")));
$this->input->emit('data', array("hello"));
$bodyStream->close();
$this->input->emit('data', array("world"));
}
public function testHandleError()
{
$this->bodyStream->on('error', $this->expectCallableOnce());
$this->bodyStream->on('close', $this->expectCallableOnce());
$this->input->emit('error', array(new \RuntimeException()));
$this->assertFalse($this->bodyStream->isReadable());
}
public function testToString()
{
$this->assertEquals('', $this->bodyStream->__toString());
}
public function testDetach()
{
$this->assertNull($this->bodyStream->detach());
}
public function testGetSizeDefault()
{
$this->assertNull($this->bodyStream->getSize());
}
public function testGetSizeCustom()
{
$stream = new HttpBodyStream($this->input, 5);
$this->assertEquals(5, $stream->getSize());
}
/**
* @expectedException BadMethodCallException
*/
public function testTell()
{
$this->bodyStream->tell();
}
/**
* @expectedException BadMethodCallException
*/
public function testEof()
{
$this->bodyStream->eof();
}
public function testIsSeekable()
{
$this->assertFalse($this->bodyStream->isSeekable());
}
/**
* @expectedException BadMethodCallException
*/
public function testWrite()
{
$this->bodyStream->write('');
}
/**
* @expectedException BadMethodCallException
*/
public function testRead()
{
$this->bodyStream->read('');
}
public function testGetContents()
{
$this->assertEquals('', $this->bodyStream->getContents());
}
public function testGetMetaData()
{
$this->assertNull($this->bodyStream->getMetadata());
}
public function testIsReadable()
{
$this->assertTrue($this->bodyStream->isReadable());
}
/**
* @expectedException BadMethodCallException
*/
public function testSeek()
{
$this->bodyStream->seek('');
}
/**
* @expectedException BadMethodCallException
*/
public function testRewind()
{
$this->bodyStream->rewind();
}
public function testIsWriteable()
{
$this->assertFalse($this->bodyStream->isWritable());
}
}
http-0.8.3/tests/Io/IniUtilTest.php 0000664 0000000 0000000 00000003052 13263421677 0017153 0 ustar 00root root 0000000 0000000 assertEquals($output, IniUtil::iniSizeToBytes($input));
}
public function provideInvalidInputIniSizeToBytes()
{
return array(
array('-1G'),
array('0G'),
array(null),
array('foo'),
array('fooK'),
array('1ooL'),
array('1ooL'),
);
}
/**
* @dataProvider provideInvalidInputIniSizeToBytes
* @expectedException InvalidArgumentException
*/
public function testInvalidInputIniSizeToBytes($input)
{
IniUtil::iniSizeToBytes($input);
}
}
http-0.8.3/tests/Io/LengthLimitedStreamTest.php 0000664 0000000 0000000 00000007043 13263421677 0021507 0 ustar 00root root 0000000 0000000 input = new ThroughStream();
}
public function testSimpleChunk()
{
$stream = new LengthLimitedStream($this->input, 5);
$stream->on('data', $this->expectCallableOnceWith('hello'));
$stream->on('end', $this->expectCallableOnce());
$this->input->emit('data', array("hello world"));
}
public function testInputStreamKeepsEmitting()
{
$stream = new LengthLimitedStream($this->input, 5);
$stream->on('data', $this->expectCallableOnceWith('hello'));
$stream->on('end', $this->expectCallableOnce());
$this->input->emit('data', array("hello world"));
$this->input->emit('data', array("world"));
$this->input->emit('data', array("world"));
}
public function testZeroLengthInContentLengthWillIgnoreEmittedDataEvents()
{
$stream = new LengthLimitedStream($this->input, 0);
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableOnce());
$this->input->emit('data', array("hello world"));
}
public function testHandleError()
{
$stream = new LengthLimitedStream($this->input, 0);
$stream->on('error', $this->expectCallableOnce());
$stream->on('close', $this->expectCallableOnce());
$this->input->emit('error', array(new \RuntimeException()));
$this->assertFalse($stream->isReadable());
}
public function testPauseStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$stream = new LengthLimitedStream($input, 0);
$stream->pause();
}
public function testResumeStream()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$stream = new LengthLimitedStream($input, 0);
$stream->pause();
$stream->resume();
}
public function testPipeStream()
{
$stream = new LengthLimitedStream($this->input, 0);
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = $stream->pipe($dest);
$this->assertSame($dest, $ret);
}
public function testHandleClose()
{
$stream = new LengthLimitedStream($this->input, 0);
$stream->on('close', $this->expectCallableOnce());
$this->input->close();
$this->input->emit('end', array());
$this->assertFalse($stream->isReadable());
}
public function testOutputStreamCanCloseInputStream()
{
$input = new ThroughStream();
$input->on('close', $this->expectCallableOnce());
$stream = new LengthLimitedStream($input, 0);
$stream->on('close', $this->expectCallableOnce());
$stream->close();
$this->assertFalse($input->isReadable());
}
public function testHandleUnexpectedEnd()
{
$stream = new LengthLimitedStream($this->input, 5);
$stream->on('data', $this->expectCallableNever());
$stream->on('close', $this->expectCallableOnce());
$stream->on('end', $this->expectCallableNever());
$stream->on('error', $this->expectCallableOnce());
$this->input->emit('end');
}
}
http-0.8.3/tests/Io/MiddlewareRunnerTest.php 0000664 0000000 0000000 00000037460 13263421677 0021057 0 ustar 00root root 0000000 0000000 assertEquals(2, $args);
}
public function testFinalHandlerReceivesOneArgument()
{
$args = null;
$middleware = new MiddlewareRunner(array(
function (ServerRequestInterface $request) use (&$args) {
$args = func_num_args();
return null;
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$middleware($request);
$this->assertEquals(1, $args);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage hello
*/
public function testThrowsIfHandlerThrowsException()
{
$middleware = new MiddlewareRunner(array(
function (ServerRequestInterface $request) {
throw new \RuntimeException('hello');
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$middleware($request);
}
/**
* @requires PHP 7
* @expectedException Throwable
* @expectedExceptionMessage hello
*/
public function testThrowsIfHandlerThrowsThrowable()
{
$middleware = new MiddlewareRunner(array(
function (ServerRequestInterface $request) {
throw new \Error('hello');
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$middleware($request);
}
public function provideProcessStackMiddlewares()
{
$processStackA = new ProcessStack();
$processStackB = new ProcessStack();
$processStackC = new ProcessStack();
$processStackD = new ProcessStack();
$responseMiddleware = function () {
return new Response(200);
};
return array(
array(
array(
$processStackA,
$responseMiddleware,
),
1,
),
array(
array(
$processStackB,
$processStackB,
$responseMiddleware,
),
2,
),
array(
array(
$processStackC,
$processStackC,
$processStackC,
$responseMiddleware,
),
3,
),
array(
array(
$processStackD,
$processStackD,
$processStackD,
$processStackD,
$responseMiddleware,
),
4,
),
);
}
/**
* @dataProvider provideProcessStackMiddlewares
*/
public function testProcessStack(array $middlewares, $expectedCallCount)
{
// the ProcessStack middleware instances are stateful, so reset these
// before running the test, to not fail with --repeat=100
foreach ($middlewares as $middleware) {
if ($middleware instanceof ProcessStack) {
$middleware->reset();
}
}
$request = new ServerRequest('GET', 'https://example.com/');
$middlewareStack = new MiddlewareRunner($middlewares);
$response = $middlewareStack($request);
$this->assertTrue($response instanceof PromiseInterface);
$response = Block\await($response, Factory::create());
$this->assertTrue($response instanceof ResponseInterface);
$this->assertSame(200, $response->getStatusCode());
foreach ($middlewares as $middleware) {
if (!($middleware instanceof ProcessStack)) {
continue;
}
$this->assertSame($expectedCallCount, $middleware->getCallCount());
}
}
public function provideErrorHandler()
{
return array(
array(
function (\Exception $e) {
throw $e;
}
),
array(
function (\Exception $e) {
return Promise\reject($e);
}
)
);
}
/**
* @dataProvider provideErrorHandler
*/
public function testNextCanBeRunMoreThanOnceWithoutCorruptingTheMiddlewareStack($errorHandler)
{
$exception = new \RuntimeException('exception');
$retryCalled = 0;
$error = null;
$retry = function ($request, $next) use (&$error, &$retryCalled) {
$promise = new \React\Promise\Promise(function ($resolve) use ($request, $next) {
$resolve($next($request));
});
return $promise->then(null, function ($et) use (&$error, $request, $next, &$retryCalled) {
$retryCalled++;
$error = $et;
// the $next failed. discard $error and retry once again:
return $next($request);
});
};
$response = new Response();
$called = 0;
$runner = new MiddlewareRunner(array(
$retry,
function () use ($errorHandler, &$called, $response, $exception) {
$called++;
if ($called === 1) {
return $errorHandler($exception);
}
return $response;
}
));
$request = new ServerRequest('GET', 'https://example.com/');
$this->assertSame($response, Block\await($runner($request), Factory::create()));
$this->assertSame(1, $retryCalled);
$this->assertSame(2, $called);
$this->assertSame($exception, $error);
}
public function testMultipleRunsInvokeAllMiddlewareInCorrectOrder()
{
$requests = array(
new ServerRequest('GET', 'https://example.com/1'),
new ServerRequest('GET', 'https://example.com/2'),
new ServerRequest('GET', 'https://example.com/3')
);
$receivedRequests = array();
$middlewareRunner = new MiddlewareRunner(array(
function (ServerRequestInterface $request, $next) use (&$receivedRequests) {
$receivedRequests[] = 'middleware1: ' . $request->getUri();
return $next($request);
},
function (ServerRequestInterface $request, $next) use (&$receivedRequests) {
$receivedRequests[] = 'middleware2: ' . $request->getUri();
return $next($request);
},
function (ServerRequestInterface $request) use (&$receivedRequests) {
$receivedRequests[] = 'middleware3: ' . $request->getUri();
return new \React\Promise\Promise(function () { });
}
));
foreach ($requests as $request) {
$middlewareRunner($request);
}
$this->assertEquals(
array(
'middleware1: https://example.com/1',
'middleware2: https://example.com/1',
'middleware3: https://example.com/1',
'middleware1: https://example.com/2',
'middleware2: https://example.com/2',
'middleware3: https://example.com/2',
'middleware1: https://example.com/3',
'middleware2: https://example.com/3',
'middleware3: https://example.com/3'
),
$receivedRequests
);
}
public function provideUncommonMiddlewareArrayFormats()
{
return array(
array(
function () {
$sequence = '';
// Numeric index gap
return array(
0 => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'A';
return $next($request);
},
2 => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'B';
return $next($request);
},
3 => function () use (&$sequence) {
return new Response(200, array(), $sequence . 'C');
},
);
},
'ABC',
),
array(
function () {
$sequence = '';
// Reversed numeric indexes
return array(
2 => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'A';
return $next($request);
},
1 => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'B';
return $next($request);
},
0 => function () use (&$sequence) {
return new Response(200, array(), $sequence . 'C');
},
);
},
'ABC',
),
array(
function () {
$sequence = '';
// Associative array
return array(
'middleware1' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'A';
return $next($request);
},
'middleware2' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'B';
return $next($request);
},
'middleware3' => function () use (&$sequence) {
return new Response(200, array(), $sequence . 'C');
},
);
},
'ABC',
),
array(
function () {
$sequence = '';
// Associative array with empty or trimmable string keys
return array(
'' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'A';
return $next($request);
},
' ' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'B';
return $next($request);
},
' ' => function () use (&$sequence) {
return new Response(200, array(), $sequence . 'C');
},
);
},
'ABC',
),
array(
function () {
$sequence = '';
// Mixed array keys
return array(
'' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'A';
return $next($request);
},
0 => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'B';
return $next($request);
},
'foo' => function (ServerRequestInterface $request, $next) use (&$sequence) {
$sequence .= 'C';
return $next($request);
},
2 => function () use (&$sequence) {
return new Response(200, array(), $sequence . 'D');
},
);
},
'ABCD',
),
);
}
/**
* @dataProvider provideUncommonMiddlewareArrayFormats
*/
public function testUncommonMiddlewareArrayFormats($middlewareFactory, $expectedSequence)
{
$request = new ServerRequest('GET', 'https://example.com/');
$middlewareStack = new MiddlewareRunner($middlewareFactory());
$response = $middlewareStack($request);
$this->assertTrue($response instanceof ResponseInterface);
$this->assertSame($expectedSequence, (string) $response->getBody());
}
public function testPendingNextRequestHandlersCanBeCalledConcurrently()
{
$called = 0;
$middleware = new MiddlewareRunner(array(
function (RequestInterface $request, $next) {
$first = $next($request);
$second = $next($request);
return new Response();
},
function (RequestInterface $request) use (&$called) {
++$called;
return new Promise\Promise(function () { });
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$response = $middleware($request);
$this->assertTrue($response instanceof ResponseInterface);
$this->assertEquals(2, $called);
}
public function testCancelPendingNextHandler()
{
$once = $this->expectCallableOnce();
$middleware = new MiddlewareRunner(array(
function (RequestInterface $request, $next) {
$ret = $next($request);
$ret->cancel();
return $ret;
},
function (RequestInterface $request) use ($once) {
return new Promise\Promise(function () { }, $once);
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$middleware($request);
}
public function testCancelResultingPromiseWillCancelPendingNextHandler()
{
$once = $this->expectCallableOnce();
$middleware = new MiddlewareRunner(array(
function (RequestInterface $request, $next) {
return $next($request);
},
function (RequestInterface $request) use ($once) {
return new Promise\Promise(function () { }, $once);
}
));
$request = new ServerRequest('GET', 'http://example.com/');
$promise = $middleware($request);
$this->assertTrue($promise instanceof CancellablePromiseInterface);
$promise->cancel();
}
}
http-0.8.3/tests/Io/MultipartParserTest.php 0000664 0000000 0000000 00000104765 13263421677 0020751 0 ustar 00root root 0000000 0000000 'multipart/form-data',
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testPostKey()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[one]\"\r\n";
$data .= "\r\n";
$data .= "single\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[two]\"\r\n";
$data .= "\r\n";
$data .= "second\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'users' => array(
'one' => 'single',
'two' => 'second',
),
),
$parsedRequest->getParsedBody()
);
}
public function testPostStringOverwritesMap()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[one]\"\r\n";
$data .= "\r\n";
$data .= "ignored\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users\"\r\n";
$data .= "\r\n";
$data .= "2\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'users' => '2'
),
$parsedRequest->getParsedBody()
);
}
public function testPostMapOverwritesString()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users\"\r\n";
$data .= "\r\n";
$data .= "ignored\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[two]\"\r\n";
$data .= "\r\n";
$data .= "2\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'users' => array(
'two' => '2',
),
),
$parsedRequest->getParsedBody()
);
}
public function testPostVectorOverwritesString()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users\"\r\n";
$data .= "\r\n";
$data .= "ignored\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[]\"\r\n";
$data .= "\r\n";
$data .= "2\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'users' => array(
'2',
),
),
$parsedRequest->getParsedBody()
);
}
public function testPostDeeplyNestedArray()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[][]\"\r\n";
$data .= "\r\n";
$data .= "1\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[][]\"\r\n";
$data .= "\r\n";
$data .= "2\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'users' => array(
array(
'1'
),
array(
'2'
)
),
),
$parsedRequest->getParsedBody()
);
}
public function testEmptyPostValue()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$data .= "\r\n";
$data .= "\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'key' => ''
),
$parsedRequest->getParsedBody()
);
}
public function testEmptyPostKey()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'' => 'value'
),
$parsedRequest->getParsedBody()
);
}
public function testNestedPostKeyAssoc()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"a[b][c]\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'a' => array(
'b' => array(
'c' => 'value'
)
)
),
$parsedRequest->getParsedBody()
);
}
public function testNestedPostKeyVector()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"a[][]\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'a' => array(
array(
'value'
)
)
),
$parsedRequest->getParsedBody()
);
}
public function testFileUpload()
{
$boundary = "---------------------------12758086162038677464950549563";
$file = base64_decode("R0lGODlhAQABAIAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==");
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n";
$data .= "\r\n";
$data .= "12000\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[one]\"\r\n";
$data .= "\r\n";
$data .= "single\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[two]\"\r\n";
$data .= "\r\n";
$data .= "second\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-disposition: form-data; name=\"user\"\r\n";
$data .= "\r\n";
$data .= "single\r\n";
$data .= "--$boundary\r\n";
$data .= "content-Disposition: form-data; name=\"user2\"\r\n";
$data .= "\r\n";
$data .= "second\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[]\"\r\n";
$data .= "\r\n";
$data .= "first in array\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[]\"\r\n";
$data .= "\r\n";
$data .= "second in array\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"Us er.php\"\r\n";
$data .= "Content-type: text/php\r\n";
$data .= "\r\n";
$data .= " 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertSame(
array(
'MAX_FILE_SIZE' => '12000',
'users' => array(
'one' => 'single',
'two' => 'second',
0 => 'first in array',
1 => 'second in array',
),
'user' => 'single',
'user2' => 'second',
),
$parsedRequest->getParsedBody()
);
$files = $parsedRequest->getUploadedFiles();
$this->assertTrue(isset($files['file']));
$this->assertCount(3, $files['files']);
$this->assertSame('Us er.php', $files['file']->getClientFilename());
$this->assertSame('text/php', $files['file']->getClientMediaType());
$this->assertSame("getStream());
$this->assertSame('blank.gif', $files['files'][0]->getClientFilename());
$this->assertSame('image/gif', $files['files'][0]->getClientMediaType());
$this->assertSame($file, (string)$files['files'][0]->getStream());
$this->assertSame('User.php', $files['files'][1]->getClientFilename());
$this->assertSame('text/php', $files['files'][1]->getClientMediaType());
$this->assertSame("getStream());
$this->assertSame('Owner.php', $files['files'][2]->getClientFilename());
$this->assertSame('text/php', $files['files'][2]->getClientMediaType());
$this->assertSame("getStream());
}
public function testInvalidDoubleContentDispositionUsesLast()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"ignored\"\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
array(
'key' => 'value'
),
$parsedRequest->getParsedBody()
);
}
public function testInvalidMissingNewlineAfterValueWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$data .= "\r\n";
$data .= "value";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidMissingValueWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$data .= "\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidMissingValueAndEndBoundaryWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidContentDispositionMissingWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Type: text/plain\r\n";
$data .= "\r\n";
$data .= "hello\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidContentDispositionMissingValueWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidContentDispositionWithoutNameWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; something=\"key\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testInvalidMissingEndBoundaryWillBeIgnored()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"key\"\r\n";
$data .= "\r\n";
$data .= "value\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$this->assertEmpty($parsedRequest->getUploadedFiles());
$this->assertSame(
null,
$parsedRequest->getParsedBody()
);
}
public function testInvalidUploadFileWithoutContentTypeUsesNullValue()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('hello.txt', $file->getClientFilename());
$this->assertNull($file->getClientMediaType());
$this->assertSame(5, $file->getSize());
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
$this->assertSame('world', (string)$file->getStream());
}
public function testInvalidUploadFileWithoutMultipleContentTypeUsesLastValue()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\n";
$data .= "Content-Type: text/ignored\r\n";
$data .= "Content-Type: text/plain\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('hello.txt', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(5, $file->getSize());
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
$this->assertSame('world', (string)$file->getStream());
}
public function testUploadEmptyFile()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"empty\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('empty', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(0, $file->getSize());
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
$this->assertSame('', (string)$file->getStream());
}
public function testUploadTooLargeFile()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"hello\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser(4);
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('hello', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(5, $file->getSize());
$this->assertSame(UPLOAD_ERR_INI_SIZE, $file->getError());
}
public function testUploadTooLargeFileWithIniLikeSize()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"hello\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= str_repeat('world', 1024) . "\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser('1K');
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('hello', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(5120, $file->getSize());
$this->assertSame(UPLOAD_ERR_INI_SIZE, $file->getError());
}
public function testUploadNoFile()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"\"\r\n";
$data .= "Content-type: application/octet-stream\r\n";
$data .= "\r\n";
$data .= "\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['file']));
$this->assertInstanceOf('Psr\Http\Message\UploadedFileInterface', $files['file']);
/* @var $file \Psr\Http\Message\UploadedFileInterface */
$file = $files['file'];
$this->assertSame('', $file->getClientFilename());
$this->assertSame('application/octet-stream', $file->getClientMediaType());
$this->assertSame(0, $file->getSize());
$this->assertSame(UPLOAD_ERR_NO_FILE, $file->getError());
}
public function testUploadTooManyFilesReturnsTruncatedList()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"first\"; filename=\"first\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "hello\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"second\"; filename=\"second\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser(100, 1);
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $files);
$this->assertTrue(isset($files['first']));
$file = $files['first'];
$this->assertSame('first', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(5, $file->getSize());
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
$this->assertSame('hello', (string)$file->getStream());
}
public function testUploadTooManyFilesIgnoresEmptyFilesAndIncludesThemDespiteTruncatedList()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"first\"; filename=\"first\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "hello\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"empty\"; filename=\"\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"second\"; filename=\"second\"\r\n";
$data .= "Content-type: text/plain\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser(100, 1);
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertCount(2, $files);
$this->assertTrue(isset($files['first']));
$this->assertTrue(isset($files['empty']));
$file = $files['first'];
$this->assertSame('first', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(5, $file->getSize());
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
$this->assertSame('hello', (string)$file->getStream());
$file = $files['empty'];
$this->assertSame('', $file->getClientFilename());
$this->assertSame('text/plain', $file->getClientMediaType());
$this->assertSame(0, $file->getSize());
$this->assertSame(UPLOAD_ERR_NO_FILE, $file->getError());
}
public function testPostMaxFileSize()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n";
$data .= "\r\n";
$data .= "12\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"User.php\"\r\n";
$data .= "Content-type: text/php\r\n";
$data .= "\r\n";
$data .= " 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertTrue(isset($files['file']));
$this->assertSame(UPLOAD_ERR_FORM_SIZE, $files['file']->getError());
}
public function testPostMaxFileSizeIgnoredByFilesComingBeforeIt()
{
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"file\"; filename=\"User-one.php\"\r\n";
$data .= "Content-type: text/php\r\n";
$data .= "\r\n";
$data .= " 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
$parser = new MultipartParser();
$parsedRequest = $parser->parse($request);
$files = $parsedRequest->getUploadedFiles();
$this->assertTrue(isset($files['file']));
$this->assertSame(UPLOAD_ERR_OK, $files['file']->getError());
$this->assertTrue(isset($files['file2']));
$this->assertSame(UPLOAD_ERR_OK, $files['file2']->getError());
$this->assertTrue(isset($files['file3']));
$this->assertSame(UPLOAD_ERR_FORM_SIZE, $files['file3']->getError());
$this->assertTrue(isset($files['file4']));
$this->assertSame(UPLOAD_ERR_OK, $files['file4']->getError());
}
} http-0.8.3/tests/Io/PauseBufferStreamTest.php 0000664 0000000 0000000 00000016056 13263421677 0021171 0 ustar 00root root 0000000 0000000 getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('pause');
$stream = new PauseBufferStream($input);
$stream->pause();
}
public function testCloseMethodWillBePassedThroughToInput()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->once())->method('close');
$stream = new PauseBufferStream($input);
$stream->close();
}
public function testPauseMethodWillNotBePassedThroughToInputAfterClose()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$input->expects($this->never())->method('pause');
$stream = new PauseBufferStream($input);
$stream->close();
$stream->pause();
}
public function testDataEventWillBePassedThroughAsIs()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->on('data', $this->expectCallableOnceWith('hello'));
$input->write('hello');
}
public function testDataEventWillBePipedThroughAsIs()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$output = new ThroughStream($this->expectCallableOnceWith('hello'));
$stream->pipe($output);
$input->write('hello');
}
public function testPausedStreamWillNotPassThroughDataEvent()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('data', $this->expectCallableNever());
$input->write('hello');
}
public function testPauseStreamWillNotPipeThroughDataEvent()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$output = new ThroughStream($this->expectCallableNever());
$stream->pipe($output);
$stream->pause();
$input->write('hello');
}
public function testPausedStreamWillPassThroughDataEventOnResume()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$input->write('hello');
$stream->on('data', $this->expectCallableOnceWith('hello'));
$stream->resume();
}
public function testEndEventWillBePassedThroughAsIs()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->on('data', $this->expectCallableOnceWith('hello'));
$stream->on('end', $this->expectCallableOnce());
$stream->on('close', $this->expectCallableOnce());
$input->end('hello');
$this->assertFalse($stream->isReadable());
}
public function testPausedStreamWillNotPassThroughEndEvent()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableNever());
$stream->on('close', $this->expectCallableNever());
$input->end('hello');
$this->assertTrue($stream->isReadable());
}
public function testPausedStreamWillPassThroughEndEventOnResume()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$input->end('hello');
$stream->on('data', $this->expectCallableOnceWith('hello'));
$stream->on('end', $this->expectCallableOnce());
$stream->on('close', $this->expectCallableOnce());
$stream->resume();
$this->assertFalse($stream->isReadable());
}
public function testPausedStreamWillNotPassThroughEndEventOnExplicitClose()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableNever());
$stream->on('close', $this->expectCallableOnce());
$input->end('hello');
$stream->close();
$this->assertFalse($stream->isReadable());
}
public function testErrorEventWillBePassedThroughAsIs()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->on('error', $this->expectCallableOnce());
$stream->on('close', $this->expectCallableOnce());
$input->emit('error', array(new \RuntimeException()));
}
public function testPausedStreamWillNotPassThroughErrorEvent()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('error', $this->expectCallableNever());
$stream->on('close', $this->expectCallableNever());
$input->emit('error', array(new \RuntimeException()));
}
public function testPausedStreamWillPassThroughErrorEventOnResume()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$input->emit('error', array(new \RuntimeException()));
$stream->on('error', $this->expectCallableOnce());
$stream->on('close', $this->expectCallableOnce());
$stream->resume();
}
public function testPausedStreamWillNotPassThroughErrorEventOnExplicitClose()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('error', $this->expectCallableNever());
$stream->on('close', $this->expectCallableOnce());
$input->emit('error', array(new \RuntimeException()));
$stream->close();
}
public function testCloseEventWillBePassedThroughAsIs()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableNever());
$stream->on('close', $this->expectCallableOnce());
$input->close();
}
public function testPausedStreamWillNotPassThroughCloseEvent()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableNever());
$stream->on('close', $this->expectCallableNever());
$input->close();
}
public function testPausedStreamWillPassThroughCloseEventOnResume()
{
$input = new ThroughStream();
$stream = new PauseBufferStream($input);
$stream->pause();
$input->close();
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableNever());
$stream->on('close', $this->expectCallableOnce());
$stream->resume();
}
}
http-0.8.3/tests/Io/RequestHeaderParserTest.php 0000664 0000000 0000000 00000041326 13263421677 0021522 0 ustar 00root root 0000000 0000000 on('headers', $this->expectCallableNever());
$parser->feed("GET / HTTP/1.1\r\n");
$parser->feed("Host: example.com:80\r\n");
$parser->feed("Connection: close\r\n");
$parser->removeAllListeners();
$parser->on('headers', $this->expectCallableOnce());
$parser->feed("\r\n");
}
public function testFeedInOneGo()
{
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableOnce());
$data = $this->createGetRequest();
$parser->feed($data);
}
public function testHeadersEventShouldReturnRequestAndBodyBuffer()
{
$request = null;
$bodyBuffer = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$request, &$bodyBuffer) {
$request = $parsedRequest;
$bodyBuffer = $parsedBodyBuffer;
});
$data = $this->createGetRequest();
$data .= 'RANDOM DATA';
$parser->feed($data);
$this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request);
$this->assertSame('GET', $request->getMethod());
$this->assertEquals('http://example.com/', $request->getUri());
$this->assertSame('1.1', $request->getProtocolVersion());
$this->assertSame(array('Host' => array('example.com'), 'Connection' => array('close')), $request->getHeaders());
$this->assertSame('RANDOM DATA', $bodyBuffer);
}
public function testHeadersEventShouldReturnBinaryBodyBuffer()
{
$bodyBuffer = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$bodyBuffer) {
$bodyBuffer = $parsedBodyBuffer;
});
$data = $this->createGetRequest();
$data .= "\0x01\0x02\0x03\0x04\0x05";
$parser->feed($data);
$this->assertSame("\0x01\0x02\0x03\0x04\0x05", $bodyBuffer);
}
public function testHeadersEventShouldParsePathAndQueryString()
{
$request = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$request) {
$request = $parsedRequest;
});
$data = $this->createAdvancedPostRequest();
$parser->feed($data);
$this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request);
$this->assertSame('POST', $request->getMethod());
$this->assertEquals('http://example.com/foo?bar=baz', $request->getUri());
$this->assertSame('1.1', $request->getProtocolVersion());
$headers = array(
'Host' => array('example.com'),
'User-Agent' => array('react/alpha'),
'Connection' => array('close'),
);
$this->assertSame($headers, $request->getHeaders());
}
public function testHeaderEventWithShouldApplyDefaultAddressFromConstructor()
{
$request = null;
$parser = new RequestHeaderParser('http://127.1.1.1:8000');
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\n\r\n");
$this->assertEquals('http://127.1.1.1:8000/foo', $request->getUri());
$this->assertEquals('127.1.1.1:8000', $request->getHeaderLine('Host'));
}
public function testHeaderEventViaHttpsShouldApplySchemeFromConstructor()
{
$request = null;
$parser = new RequestHeaderParser('https://127.1.1.1:8000');
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$this->assertEquals('https://example.com/foo', $request->getUri());
$this->assertEquals('example.com', $request->getHeaderLine('Host'));
}
public function testHeaderOverflowShouldEmitError()
{
$error = null;
$passedParser = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message, $parser) use (&$error, &$passedParser) {
$error = $message;
$passedParser = $parser;
});
$this->assertSame(1, count($parser->listeners('headers')));
$this->assertSame(1, count($parser->listeners('error')));
$data = str_repeat('A', 8193);
$parser->feed($data);
$this->assertInstanceOf('OverflowException', $error);
$this->assertSame('Maximum header size of 8192 exceeded.', $error->getMessage());
$this->assertSame($parser, $passedParser);
$this->assertSame(0, count($parser->listeners('headers')));
$this->assertSame(0, count($parser->listeners('error')));
}
public function testHeaderOverflowShouldNotEmitErrorWhenDataExceedsMaxHeaderSize()
{
$request = null;
$bodyBuffer = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$request, &$bodyBuffer) {
$request = $parsedRequest;
$bodyBuffer = $parsedBodyBuffer;
});
$data = $this->createAdvancedPostRequest();
$body = str_repeat('A', 8193 - strlen($data));
$data .= $body;
$parser->feed($data);
$headers = array(
'Host' => array('example.com'),
'User-Agent' => array('react/alpha'),
'Connection' => array('close'),
);
$this->assertSame($headers, $request->getHeaders());
$this->assertSame($body, $bodyBuffer);
}
public function testInvalidEmptyRequestHeadersParseException()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$this->assertSame(1, count($parser->listeners('headers')));
$this->assertSame(1, count($parser->listeners('error')));
$parser->feed("\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Unable to parse invalid request-line', $error->getMessage());
$this->assertSame(0, count($parser->listeners('headers')));
$this->assertSame(0, count($parser->listeners('error')));
}
public function testInvalidMalformedRequestLineParseException()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$this->assertSame(1, count($parser->listeners('headers')));
$this->assertSame(1, count($parser->listeners('error')));
$parser->feed("GET /\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Unable to parse invalid request-line', $error->getMessage());
$this->assertSame(0, count($parser->listeners('headers')));
$this->assertSame(0, count($parser->listeners('error')));
}
public function testInvalidAbsoluteFormSchemeEmitsError()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET tcp://example.com:80/ HTTP/1.0\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Invalid absolute-form request-target', $error->getMessage());
}
public function testOriginFormWithSchemeSeparatorInParam()
{
$request = null;
$parser = new RequestHeaderParser();
$parser->on('error', $this->expectCallableNever());
$parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /somepath?param=http://example.com HTTP/1.1\r\nHost: localhost\r\n\r\n");
$this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request);
$this->assertSame('GET', $request->getMethod());
$this->assertEquals('http://localhost/somepath?param=http://example.com', $request->getUri());
$this->assertSame('1.1', $request->getProtocolVersion());
$headers = array(
'Host' => array('localhost')
);
$this->assertSame($headers, $request->getHeaders());
}
public function testUriStartingWithColonSlashSlashFails()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET ://example.com:80/ HTTP/1.0\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Invalid request string', $error->getMessage());
}
public function testInvalidAbsoluteFormWithFragmentEmitsError()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET http://example.com:80/#home HTTP/1.0\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Invalid absolute-form request-target', $error->getMessage());
}
public function testInvalidHeaderContainsFullUri()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET / HTTP/1.1\r\nHost: http://user:pass@host/\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Invalid Host header value', $error->getMessage());
}
public function testInvalidAbsoluteFormWithHostHeaderEmpty()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET http://example.com/ HTTP/1.1\r\nHost: \r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('Invalid Host header value', $error->getMessage());
}
public function testInvalidConnectRequestWithNonAuthorityForm()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("CONNECT http://example.com:8080/ HTTP/1.1\r\nHost: example.com:8080\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame('CONNECT method MUST use authority-form request target', $error->getMessage());
}
public function testInvalidHttpVersion()
{
$error = null;
$parser = new RequestHeaderParser();
$parser->on('headers', $this->expectCallableNever());
$parser->on('error', function ($message) use (&$error) {
$error = $message;
});
$parser->feed("GET / HTTP/1.2\r\n\r\n");
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame(505, $error->getCode());
$this->assertSame('Received request with invalid protocol version', $error->getMessage());
}
public function testServerParamsWillBeSetOnHttpsRequest()
{
$request = null;
$parser = new RequestHeaderParser(
'https://127.1.1.1:8000',
'https://192.168.1.1:8001'
);
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();
$this->assertEquals('on', $serverParams['HTTPS']);
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);
$this->assertEquals('127.1.1.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8000', $serverParams['SERVER_PORT']);
$this->assertEquals('192.168.1.1', $serverParams['REMOTE_ADDR']);
$this->assertEquals('8001', $serverParams['REMOTE_PORT']);
}
public function testServerParamsWillBeSetOnHttpRequest()
{
$request = null;
$parser = new RequestHeaderParser(
'http://127.1.1.1:8000',
'http://192.168.1.1:8001'
);
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();
$this->assertArrayNotHasKey('HTTPS', $serverParams);
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);
$this->assertEquals('127.1.1.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8000', $serverParams['SERVER_PORT']);
$this->assertEquals('192.168.1.1', $serverParams['REMOTE_ADDR']);
$this->assertEquals('8001', $serverParams['REMOTE_PORT']);
}
public function testServerParamsWillNotSetRemoteAddressForUnixDomainSockets()
{
$request = null;
$parser = new RequestHeaderParser(
'unix://./server.sock',
null
);
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();
$this->assertArrayNotHasKey('HTTPS', $serverParams);
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);
$this->assertArrayNotHasKey('SERVER_ADDR', $serverParams);
$this->assertArrayNotHasKey('SERVER_PORT', $serverParams);
$this->assertArrayNotHasKey('REMOTE_ADDR', $serverParams);
$this->assertArrayNotHasKey('REMOTE_PORT', $serverParams);
}
public function testServerParamsWontBeSetOnMissingUrls()
{
$request = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);
$this->assertArrayNotHasKey('SERVER_ADDR', $serverParams);
$this->assertArrayNotHasKey('SERVER_PORT', $serverParams);
$this->assertArrayNotHasKey('REMOTE_ADDR', $serverParams);
$this->assertArrayNotHasKey('REMOTE_PORT', $serverParams);
}
public function testQueryParmetersWillBeSet()
{
$request = null;
$parser = new RequestHeaderParser();
$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});
$parser->feed("GET /foo.php?hello=world&test=this HTTP/1.0\r\nHost: example.com\r\n\r\n");
$queryParams = $request->getQueryParams();
$this->assertEquals('world', $queryParams['hello']);
$this->assertEquals('this', $queryParams['test']);
}
private function createGetRequest()
{
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "\r\n";
return $data;
}
private function createAdvancedPostRequest()
{
$data = "POST /foo?bar=baz HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "User-Agent: react/alpha\r\n";
$data .= "Connection: close\r\n";
$data .= "\r\n";
return $data;
}
}
http-0.8.3/tests/Io/ServerRequestTest.php 0000664 0000000 0000000 00000014722 13263421677 0020423 0 ustar 00root root 0000000 0000000 request = new ServerRequest('GET', 'http://localhost');
}
public function testGetNoAttributes()
{
$this->assertEquals(array(), $this->request->getAttributes());
}
public function testWithAttribute()
{
$request = $this->request->withAttribute('hello', 'world');
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('hello' => 'world'), $request->getAttributes());
}
public function testGetAttribute()
{
$request = $this->request->withAttribute('hello', 'world');
$this->assertNotSame($request, $this->request);
$this->assertEquals('world', $request->getAttribute('hello'));
}
public function testGetDefaultAttribute()
{
$request = $this->request->withAttribute('hello', 'world');
$this->assertNotSame($request, $this->request);
$this->assertNull($request->getAttribute('hi', null));
}
public function testWithoutAttribute()
{
$request = $this->request->withAttribute('hello', 'world');
$request = $request->withAttribute('test', 'nice');
$request = $request->withoutAttribute('hello');
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'nice'), $request->getAttributes());
}
public function testWithCookieParams()
{
$request = $this->request->withCookieParams(array('test' => 'world'));
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'world'), $request->getCookieParams());
}
public function testWithQueryParams()
{
$request = $this->request->withQueryParams(array('test' => 'world'));
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'world'), $request->getQueryParams());
}
public function testWithUploadedFiles()
{
$request = $this->request->withUploadedFiles(array('test' => 'world'));
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'world'), $request->getUploadedFiles());
}
public function testWithParsedBody()
{
$request = $this->request->withParsedBody(array('test' => 'world'));
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'world'), $request->getParsedBody());
}
public function testServerRequestParameter()
{
$body = 'hello=world';
$request = new ServerRequest(
'POST',
'http://127.0.0.1',
array('Content-Length' => strlen($body)),
$body,
'1.0',
array('SERVER_ADDR' => '127.0.0.1')
);
$serverParams = $request->getServerParams();
$this->assertEquals('POST', $request->getMethod());
$this->assertEquals('http://127.0.0.1', $request->getUri());
$this->assertEquals('11', $request->getHeaderLine('Content-Length'));
$this->assertEquals('hello=world', $request->getBody());
$this->assertEquals('1.0', $request->getProtocolVersion());
$this->assertEquals('127.0.0.1', $serverParams['SERVER_ADDR']);
}
public function testParseSingleCookieNameValuePairWillReturnValidArray()
{
$cookieString = 'hello=world';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'world'), $cookies);
}
public function testParseMultipleCookieNameValuePaiWillReturnValidArray()
{
$cookieString = 'hello=world; test=abc';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'world', 'test' => 'abc'), $cookies);
}
public function testParseMultipleCookieNameValuePairWillReturnFalse()
{
// Could be done through multiple 'Cookie' headers
// getHeaderLine('Cookie') will return a value seperated by comma
// e.g.
// GET / HTTP/1.1\r\n
// Host: test.org\r\n
// Cookie: hello=world\r\n
// Cookie: test=abc\r\n\r\n
$cookieString = 'hello=world,test=abc';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(false, $cookies);
}
public function testOnlyFirstSetWillBeAddedToCookiesArray()
{
$cookieString = 'hello=world; hello=abc';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'abc'), $cookies);
}
public function testOtherEqualSignsWillBeAddedToValueAndWillReturnValidArray()
{
$cookieString = 'hello=world=test=php';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'world=test=php'), $cookies);
}
public function testSingleCookieValueInCookiesReturnsEmptyArray()
{
$cookieString = 'world';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array(), $cookies);
}
public function testSingleMutlipleCookieValuesReturnsEmptyArray()
{
$cookieString = 'world; test';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array(), $cookies);
}
public function testSingleValueIsValidInMultipleValueCookieWillReturnValidArray()
{
$cookieString = 'world; test=php';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('test' => 'php'), $cookies);
}
public function testUrlEncodingForValueWillReturnValidArray()
{
$cookieString = 'hello=world%21; test=100%25%20coverage';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'world!', 'test' => '100% coverage'), $cookies);
}
public function testUrlEncodingForKeyWillReturnValidArray()
{
$cookieString = 'react%3Bphp=is%20great';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('react;php' => 'is great'), $cookies);
}
public function testCookieWithoutSpaceAfterSeparatorWillBeAccepted()
{
$cookieString = 'hello=world;react=php';
$cookies = ServerRequest::parseCookie($cookieString);
$this->assertEquals(array('hello' => 'world', 'react' => 'php'), $cookies);
}
}
http-0.8.3/tests/Io/UploadedFileTest.php 0000664 0000000 0000000 00000003643 13263421677 0020141 0 ustar 00root root 0000000 0000000 moveTo('bar.foo');
}
public function testGetters()
{
$stream = new BufferStream();
$uploadedFile = new UploadedFile($stream, 0, UPLOAD_ERR_OK, 'foo.bar', 'foo/bar');
self::assertSame($stream, $uploadedFile->getStream());
self::assertSame(0, $uploadedFile->getSize());
self::assertSame(UPLOAD_ERR_OK, $uploadedFile->getError());
self::assertSame('foo.bar', $uploadedFile->getClientFilename());
self::assertSame('foo/bar', $uploadedFile->getClientMediaType());
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot retrieve stream due to upload error
*/
public function testGetStreamOnFailedUpload()
{
$stream = new BufferStream();
$uploadedFile = new UploadedFile($stream, 0, UPLOAD_ERR_NO_FILE, 'foo.bar', 'foo/bar');
$uploadedFile->getStream();
}
}
http-0.8.3/tests/Middleware/ 0000775 0000000 0000000 00000000000 13263421677 0015733 5 ustar 00root root 0000000 0000000 http-0.8.3/tests/Middleware/LimitConcurrentRequestsMiddlewareTest.php 0000664 0000000 0000000 00000044124 13263421677 0026164 0 ustar 00root root 0000000 0000000 promise();
};
/**
* The second request
*/
$requestB = new ServerRequest('GET', 'https://www.example.com/');
$deferredB = new Deferred();
$calledB = false;
$nextB = function () use (&$calledB, $deferredB) {
$calledB = true;
return $deferredB->promise();
};
/**
* The third request
*/
$requestC = new ServerRequest('GET', 'https://www.example.com/');
$calledC = false;
$nextC = function () use (&$calledC) {
$calledC = true;
};
/**
* The handler
*
*/
$limitHandlers = new LimitConcurrentRequestsMiddleware(1);
$this->assertFalse($calledA);
$this->assertFalse($calledB);
$this->assertFalse($calledC);
$limitHandlers($requestA, $nextA);
$this->assertTrue($calledA);
$this->assertFalse($calledB);
$this->assertFalse($calledC);
$limitHandlers($requestB, $nextB);
$this->assertTrue($calledA);
$this->assertFalse($calledB);
$this->assertFalse($calledC);
$limitHandlers($requestC, $nextC);
$this->assertTrue($calledA);
$this->assertFalse($calledB);
$this->assertFalse($calledC);
/**
* Ensure resolve frees up a slot
*/
$deferredA->resolve();
$this->assertTrue($calledA);
$this->assertTrue($calledB);
$this->assertFalse($calledC);
/**
* Ensure reject also frees up a slot
*/
$deferredB->reject();
$this->assertTrue($calledA);
$this->assertTrue($calledB);
$this->assertTrue($calledC);
}
public function testReturnsResponseDirectlyFromMiddlewareWhenBelowLimit()
{
$middleware = new LimitConcurrentRequestsMiddleware(1);
$response = new Response();
$ret = $middleware(new ServerRequest('GET', 'https://example.com/'), function () use ($response) {
return $response;
});
$this->assertSame($response, $ret);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage demo
*/
public function testThrowsExceptionDirectlyFromMiddlewareWhenBelowLimit()
{
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'https://example.com/'), function () {
throw new \RuntimeException('demo');
});
}
/**
* @requires PHP 7
* @expectedException Error
* @expectedExceptionMessage demo
*/
public function testThrowsErrorDirectlyFromMiddlewareWhenBelowLimit()
{
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'https://example.com/'), function () {
throw new \Error('demo');
});
}
public function testReturnsPendingPromiseChainedFromMiddlewareWhenBelowLimit()
{
$middleware = new LimitConcurrentRequestsMiddleware(1);
$deferred = new Deferred();
$ret = $middleware(new ServerRequest('GET', 'https://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$this->assertTrue($ret instanceof PromiseInterface);
}
public function testReturnsPendingPromiseFromMiddlewareWhenAboveLimit()
{
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'https://example.com/'), function () {
return new Promise(function () { });
});
$ret = $middleware(new ServerRequest('GET', 'https://example.com/'), function () {
return new Response();
});
$this->assertTrue($ret instanceof PromiseInterface);
}
public function testStreamDoesNotPauseOrResumeWhenBelowLimit()
{
$body = $this->getMockBuilder('React\Http\Io\HttpBodyStream')->disableOriginalConstructor()->getMock();
$body->expects($this->never())->method('pause');
$body->expects($this->never())->method('resume');
$limitHandlers = new LimitConcurrentRequestsMiddleware(1);
$limitHandlers(new ServerRequest('GET', 'https://example.com/', array(), $body), function () {});
}
public function testStreamDoesPauseWhenAboveLimit()
{
$body = $this->getMockBuilder('React\Http\Io\HttpBodyStream')->disableOriginalConstructor()->getMock();
$body->expects($this->once())->method('pause');
$body->expects($this->never())->method('resume');
$limitHandlers = new LimitConcurrentRequestsMiddleware(1);
$limitHandlers(new ServerRequest('GET', 'https://example.com'), function () {
return new Promise(function () { });
});
$limitHandlers(new ServerRequest('GET', 'https://example.com/', array(), $body), function () {});
}
public function testStreamDoesPauseAndThenResumeWhenDequeued()
{
$body = $this->getMockBuilder('React\Http\Io\HttpBodyStream')->disableOriginalConstructor()->getMock();
$body->expects($this->once())->method('pause');
$body->expects($this->once())->method('resume');
$limitHandlers = new LimitConcurrentRequestsMiddleware(1);
$deferred = new Deferred();
$limitHandlers(new ServerRequest('GET', 'https://example.com'), function () use ($deferred) {
return $deferred->promise();
});
$limitHandlers(new ServerRequest('GET', 'https://example.com/', array(), $body), function () {});
$deferred->reject();
}
public function testReceivesBufferedRequestSameInstance()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$req = null;
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function (ServerRequestInterface $request) use (&$req) {
$req = $request;
});
$this->assertSame($request, $req);
}
public function testReceivesStreamingBodyRequestSameInstanceWhenBelowLimit()
{
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 5)
);
$req = null;
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function (ServerRequestInterface $request) use (&$req) {
$req = $request;
});
$this->assertSame($request, $req);
$body = $req->getBody();
$body->on('data', $this->expectCallableOnce('hello'));
$stream->write('hello');
}
public function testReceivesRequestsSequentially()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, $this->expectCallableOnceWith($request));
$middleware($request, $this->expectCallableOnceWith($request));
$middleware($request, $this->expectCallableOnceWith($request));
}
public function testDoesNotReceiveNextRequestIfHandlerIsPending()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function () {
return new Promise(function () {
// NO-OP: pending promise
});
});
$middleware($request, $this->expectCallableNever());
}
public function testReceivesNextRequestAfterPreviousHandlerIsSettled()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$deferred->reject(new \RuntimeException());
$middleware($request, $this->expectCallableOnceWith($request));
}
public function testReceivesNextRequestWhichThrowsAfterPreviousHandlerIsSettled()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$second = $middleware($request, function () {
throw new \RuntimeException();
});
$this->assertTrue($second instanceof PromiseInterface);
$second->then(null, $this->expectCallableOnce());
$deferred->reject(new \RuntimeException());
}
public function testPendingRequestCanBeCancelledAndForwardsCancellationToInnerPromise()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$once = $this->expectCallableOnce();
$deferred = new Deferred(function () use ($once) {
$once();
throw new \RuntimeException('Cancelled');
});
$middleware = new LimitConcurrentRequestsMiddleware(1);
$promise = $middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$this->assertTrue($promise instanceof PromiseInterface);
$promise->cancel();
}
public function testQueuedRequestCanBeCancelledBeforeItStartsProcessing()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$promise = $middleware($request, $this->expectCallableNever());
$this->assertTrue($promise instanceof PromiseInterface);
$promise->cancel();
$promise->then(null, $this->expectCallableOnce());
}
public function testReceivesNextRequestAfterPreviousHandlerIsCancelled()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred(function () {
throw new \RuntimeException('Cancelled');
});
$middleware = new LimitConcurrentRequestsMiddleware(1);
$promise = $middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$this->assertTrue($promise instanceof PromiseInterface);
$promise->cancel();
$promise->then(null, $this->expectCallableOnce());
$middleware($request, $this->expectCallableOnceWith($request));
}
public function testRejectsWhenQueuedPromiseIsCancelled()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$first = $middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$second = $middleware($request, $this->expectCallableNever());
$this->assertTrue($second instanceof PromiseInterface);
$second->cancel();
$second->then(null, $this->expectCallableOnce());
}
public function testDoesNotInvokeNextHandlersWhenQueuedPromiseIsCancelled()
{
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
'hello'
);
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$first = $middleware($request, function () use ($deferred) {
return $deferred->promise();
});
$second = $middleware($request, $this->expectCallableNever());
/* $third = */ $middleware($request, $this->expectCallableNever());
$this->assertTrue($second instanceof PromiseInterface);
$second->cancel();
}
public function testReceivesStreamingBodyChangesInstanceWithCustomBodyButSameDataWhenDequeued()
{
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 5)
);
$middleware = new LimitConcurrentRequestsMiddleware(1);
$deferred = new Deferred();
$middleware(new ServerRequest('GET', 'https://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$req = null;
$middleware($request, function (ServerRequestInterface $request) use (&$req) {
$req = $request;
});
$deferred->reject();
$this->assertNotSame($request, $req);
$this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $req);
$body = $req->getBody();
$this->assertInstanceOf('React\Stream\ReadableStreamInterface', $body);
/* @var $body \React\Stream\ReadableStreamInterface */
$this->assertEquals(5, $body->getSize());
$body->on('data', $this->expectCallableOnce('hello'));
$stream->write('hello');
}
public function testReceivesNextStreamingBodyWithBufferedDataAfterPreviousHandlerIsSettled()
{
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'http://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 10)
);
$once = $this->expectCallableOnceWith('helloworld');
$middleware($request, function (ServerRequestInterface $request) use ($once) {
$request->getBody()->on('data', $once);
});
$stream->write('hello');
$stream->write('world');
$deferred->reject(new \RuntimeException());
}
public function testReceivesNextStreamingBodyAndDoesNotEmitDataIfExplicitlyClosed()
{
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'http://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 10)
);
$never = $this->expectCallableNever();
$middleware($request, function (ServerRequestInterface $request) use ($never) {
$request->getBody()->close();
$request->getBody()->on('data', $never);
});
$stream->write('hello');
$stream->write('world');
$deferred->reject(new \RuntimeException());
}
public function testReceivesNextStreamingBodyAndDoesNotEmitDataIfExplicitlyPaused()
{
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'http://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 10)
);
$never = $this->expectCallableNever();
$middleware($request, function (ServerRequestInterface $request) use ($never) {
$request->getBody()->pause();
$request->getBody()->on('data', $never);
});
$stream->write('hello');
$stream->write('world');
$deferred->reject(new \RuntimeException());
}
public function testReceivesNextStreamingBodyAndDoesEmitDataImmediatelyIfExplicitlyResumed()
{
$deferred = new Deferred();
$middleware = new LimitConcurrentRequestsMiddleware(1);
$middleware(new ServerRequest('GET', 'http://example.com/'), function () use ($deferred) {
return $deferred->promise();
});
$stream = new ThroughStream();
$request = new ServerRequest(
'POST',
'http://example.com/',
array(),
new HttpBodyStream($stream, 10)
);
$once = $this->expectCallableOnceWith('helloworld');
$never = $this->expectCallableNever();
$middleware($request, function (ServerRequestInterface $request) use ($once, $never) {
$request->getBody()->on('data', $once);
$request->getBody()->resume();
$request->getBody()->on('data', $never);
});
$stream->write('hello');
$stream->write('world');
$deferred->reject(new \RuntimeException());
}
}
http-0.8.3/tests/Middleware/ProcessStack.php 0000664 0000000 0000000 00000001045 13263421677 0021050 0 ustar 00root root 0000000 0000000 callCount++;
return Promise\resolve($stack($request));
}
/**
* @return int
*/
public function getCallCount()
{
return $this->callCount;
}
public function reset()
{
$this->callCount = 0;
}
}
http-0.8.3/tests/Middleware/RequestBodyBufferMiddlewareTest.php 0000664 0000000 0000000 00000020416 13263421677 0024705 0 ustar 00root root 0000000 0000000 write('hello');
$stream->write('world');
$stream->end('!');
$this->assertSame(11, $exposedRequest->getBody()->getSize());
$this->assertSame('helloworld!', $exposedRequest->getBody()->getContents());
}
public function testAlreadyBufferedResolvesImmediately()
{
$size = 1024;
$body = str_repeat('x', $size);
$stream = new BufferStream(1024);
$stream->write($body);
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
$stream
);
$exposedRequest = null;
$buffer = new RequestBodyBufferMiddleware();
$buffer(
$serverRequest,
function (ServerRequestInterface $request) use (&$exposedRequest) {
$exposedRequest = $request;
}
);
$this->assertSame($size, $exposedRequest->getBody()->getSize());
$this->assertSame($body, $exposedRequest->getBody()->getContents());
}
public function testEmptyStreamingResolvesImmediatelyWithEmptyBufferedBody()
{
$stream = new ThroughStream();
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
$body = new HttpBodyStream($stream, 0)
);
$exposedRequest = null;
$buffer = new RequestBodyBufferMiddleware();
$buffer(
$serverRequest,
function (ServerRequestInterface $request) use (&$exposedRequest) {
$exposedRequest = $request;
}
);
$this->assertSame(0, $exposedRequest->getBody()->getSize());
$this->assertSame('', $exposedRequest->getBody()->getContents());
$this->assertNotSame($body, $exposedRequest->getBody());
}
public function testEmptyBufferedResolvesImmediatelyWithSameBody()
{
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
''
);
$body = $serverRequest->getBody();
$exposedRequest = null;
$buffer = new RequestBodyBufferMiddleware();
$buffer(
$serverRequest,
function (ServerRequestInterface $request) use (&$exposedRequest) {
$exposedRequest = $request;
}
);
$this->assertSame(0, $exposedRequest->getBody()->getSize());
$this->assertSame('', $exposedRequest->getBody()->getContents());
$this->assertSame($body, $exposedRequest->getBody());
}
public function testKnownExcessiveSizedBodyIsDisgardedTheRequestIsPassedDownToTheNextMiddleware()
{
$loop = Factory::create();
$stream = new ThroughStream();
$stream->end('aa');
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
new HttpBodyStream($stream, 2)
);
$buffer = new RequestBodyBufferMiddleware(1);
$response = Block\await($buffer(
$serverRequest,
function (ServerRequestInterface $request) {
return new Response(200, array(), $request->getBody()->getContents());
}
), $loop);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('', $response->getBody()->getContents());
}
public function testKnownExcessiveSizedWithIniLikeSize()
{
$loop = Factory::create();
$stream = new ThroughStream();
$loop->addTimer(0.001, function () use ($stream) {
$stream->end(str_repeat('a', 2048));
});
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
new HttpBodyStream($stream, 2048)
);
$buffer = new RequestBodyBufferMiddleware('1K');
$response = Block\await($buffer(
$serverRequest,
function (ServerRequestInterface $request) {
return new Response(200, array(), $request->getBody()->getContents());
}
), $loop);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('', $response->getBody()->getContents());
}
public function testAlreadyBufferedExceedingSizeResolvesImmediatelyWithEmptyBody()
{
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
'hello'
);
$exposedRequest = null;
$buffer = new RequestBodyBufferMiddleware(1);
$buffer(
$serverRequest,
function (ServerRequestInterface $request) use (&$exposedRequest) {
$exposedRequest = $request;
}
);
$this->assertSame(0, $exposedRequest->getBody()->getSize());
$this->assertSame('', $exposedRequest->getBody()->getContents());
}
public function testExcessiveSizeBodyIsDiscardedAndTheRequestIsPassedDownToTheNextMiddleware()
{
$loop = Factory::create();
$stream = new ThroughStream();
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
new HttpBodyStream($stream, null)
);
$buffer = new RequestBodyBufferMiddleware(1);
$promise = $buffer(
$serverRequest,
function (ServerRequestInterface $request) {
return new Response(200, array(), $request->getBody()->getContents());
}
);
$stream->end('aa');
$exposedResponse = Block\await($promise->then(
null,
$this->expectCallableNever()
), $loop);
$this->assertSame(200, $exposedResponse->getStatusCode());
$this->assertSame('', $exposedResponse->getBody()->getContents());
}
/**
* @expectedException RuntimeException
*/
public function testBufferingErrorThrows()
{
$loop = Factory::create();
$stream = new ThroughStream();
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
new HttpBodyStream($stream, null)
);
$buffer = new RequestBodyBufferMiddleware(1);
$promise = $buffer(
$serverRequest,
function (ServerRequestInterface $request) {
return $request;
}
);
$stream->emit('error', array(new \RuntimeException()));
Block\await($promise, $loop);
}
public function testFullBodyStreamedBeforeCallingNextMiddleware()
{
$promiseResolved = false;
$middleware = new RequestBodyBufferMiddleware(3);
$stream = new ThroughStream();
$serverRequest = new ServerRequest(
'GET',
'https://example.com/',
array(),
new HttpBodyStream($stream, null)
);
$middleware($serverRequest, function () {
return new Response();
})->then(function () use (&$promiseResolved) {
$promiseResolved = true;
});
$stream->write('aaa');
$this->assertFalse($promiseResolved);
$stream->write('aaa');
$this->assertFalse($promiseResolved);
$stream->end('aaa');
$this->assertTrue($promiseResolved);
}
}
http-0.8.3/tests/Middleware/RequestBodyParserMiddlewareTest.php 0000664 0000000 0000000 00000027142 13263421677 0024733 0 ustar 00root root 0000000 0000000 'application/x-www-form-urlencoded',
),
'hello=world'
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertSame(
array('hello' => 'world'),
$parsedRequest->getParsedBody()
);
$this->assertSame('hello=world', (string)$parsedRequest->getBody());
}
public function testFormUrlencodedParsingIgnoresCaseForHeadersButRespectsContentCase()
{
$middleware = new RequestBodyParserMiddleware();
$request = new ServerRequest(
'POST',
'https://example.com/',
array(
'CONTENT-TYPE' => 'APPLICATION/X-WWW-Form-URLEncoded',
),
'Hello=World'
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertSame(
array('Hello' => 'World'),
$parsedRequest->getParsedBody()
);
$this->assertSame('Hello=World', (string)$parsedRequest->getBody());
}
public function testFormUrlencodedParsingNestedStructure()
{
$middleware = new RequestBodyParserMiddleware();
$request = new ServerRequest(
'POST',
'https://example.com/',
array(
'Content-Type' => 'application/x-www-form-urlencoded',
),
'foo=bar&baz[]=cheese&bar[]=beer&bar[]=wine&market[fish]=salmon&market[meat][]=beef&market[meat][]=chicken&market[]=bazaar'
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertSame(
array(
'foo' => 'bar',
'baz' => array(
'cheese',
),
'bar' => array(
'beer',
'wine',
),
'market' => array(
'fish' => 'salmon',
'meat' => array(
'beef',
'chicken',
),
0 => 'bazaar',
),
),
$parsedRequest->getParsedBody()
);
$this->assertSame('foo=bar&baz[]=cheese&bar[]=beer&bar[]=wine&market[fish]=salmon&market[meat][]=beef&market[meat][]=chicken&market[]=bazaar', (string)$parsedRequest->getBody());
}
public function testFormUrlencodedIgnoresBodyWithExcessiveNesting()
{
// supported in all Zend PHP versions and HHVM
// ini setting does exist everywhere but HHVM: https://3v4l.org/hXLiK
// HHVM limits to 64 and returns an empty array structure: https://3v4l.org/j3DK2
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Not supported on HHVM (limited to depth 64, but keeps empty array structure)');
}
$allowed = (int)ini_get('max_input_nesting_level');
$middleware = new RequestBodyParserMiddleware();
$request = new ServerRequest(
'POST',
'https://example.com/',
array(
'Content-Type' => 'application/x-www-form-urlencoded',
),
'hello' . str_repeat('[]', $allowed + 1) . '=world'
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertSame(
array(),
$parsedRequest->getParsedBody()
);
}
public function testFormUrlencodedTruncatesBodyWithExcessiveLength()
{
// supported as of PHP 5.3.11, no HHVM support: https://3v4l.org/PiqnQ
// ini setting already exists in PHP 5.3.9: https://3v4l.org/VF6oV
if (defined('HHVM_VERSION') || PHP_VERSION_ID < 50311) {
$this->markTestSkipped('Not supported on HHVM and PHP < 5.3.11 (unlimited length)');
}
$allowed = (int)ini_get('max_input_vars');
$middleware = new RequestBodyParserMiddleware();
$request = new ServerRequest(
'POST',
'https://example.com/',
array(
'Content-Type' => 'application/x-www-form-urlencoded',
),
str_repeat('a[]=b&', $allowed + 1)
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$body = $parsedRequest->getParsedBody();
$this->assertCount(1, $body);
$this->assertTrue(isset($body['a']));
$this->assertCount($allowed, $body['a']);
}
public function testDoesNotParseJsonByDefault()
{
$middleware = new RequestBodyParserMiddleware();
$request = new ServerRequest(
'POST',
'https://example.com/',
array(
'Content-Type' => 'application/json',
),
'{"hello":"world"}'
);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertNull($parsedRequest->getParsedBody());
$this->assertSame('{"hello":"world"}', (string)$parsedRequest->getBody());
}
public function testMultipartFormDataParsing()
{
$middleware = new RequestBodyParserMiddleware();
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[one]\"\r\n";
$data .= "\r\n";
$data .= "single\r\n";
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"users[two]\"\r\n";
$data .= "\r\n";
$data .= "second\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertSame(
array(
'users' => array(
'one' => 'single',
'two' => 'second',
),
),
$parsedRequest->getParsedBody()
);
$this->assertSame($data, (string)$parsedRequest->getBody());
}
public function testMultipartFormDataIgnoresFieldWithExcessiveNesting()
{
// supported in all Zend PHP versions and HHVM
// ini setting does exist everywhere but HHVM: https://3v4l.org/hXLiK
// HHVM limits to 64 and otherwise returns an empty array structure
$allowed = (int)ini_get('max_input_nesting_level');
if ($allowed === 0) {
$allowed = 64;
}
$middleware = new RequestBodyParserMiddleware();
$boundary = "---------------------------12758086162038677464950549563";
$data = "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"hello" . str_repeat("[]", $allowed + 1) . "\"\r\n";
$data .= "\r\n";
$data .= "world\r\n";
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$this->assertEmpty($parsedRequest->getParsedBody());
}
public function testMultipartFormDataTruncatesBodyWithExcessiveLength()
{
// ini setting exists in PHP 5.3.9, not in HHVM: https://3v4l.org/VF6oV
// otherwise default to 1000 as implemented within
$allowed = (int)ini_get('max_input_vars');
if ($allowed === 0) {
$allowed = 1000;
}
$middleware = new RequestBodyParserMiddleware();
$boundary = "---------------------------12758086162038677464950549563";
$data = "";
for ($i = 0; $i < $allowed + 1; ++$i) {
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"a[]\"\r\n";
$data .= "\r\n";
$data .= "b\r\n";
}
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$body = $parsedRequest->getParsedBody();
$this->assertCount(1, $body);
$this->assertTrue(isset($body['a']));
$this->assertCount($allowed, $body['a']);
}
public function testMultipartFormDataTruncatesExcessiveNumberOfEmptyFileUploads()
{
// ini setting exists in PHP 5.3.9, not in HHVM: https://3v4l.org/VF6oV
// otherwise default to 1000 as implemented within
$allowed = (int)ini_get('max_input_vars');
if ($allowed === 0) {
$allowed = 1000;
}
$middleware = new RequestBodyParserMiddleware();
$boundary = "---------------------------12758086162038677464950549563";
$data = "";
for ($i = 0; $i < $allowed + 1; ++$i) {
$data .= "--$boundary\r\n";
$data .= "Content-Disposition: form-data; name=\"empty[]\"; filename=\"\"\r\n";
$data .= "\r\n";
$data .= "\r\n";
}
$data .= "--$boundary--\r\n";
$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);
/** @var ServerRequestInterface $parsedRequest */
$parsedRequest = $middleware(
$request,
function (ServerRequestInterface $request) {
return $request;
}
);
$body = $parsedRequest->getUploadedFiles();
$this->assertCount(1, $body);
$this->assertTrue(isset($body['empty']));
$this->assertCount($allowed, $body['empty']);
}
}
http-0.8.3/tests/ResponseTest.php 0000664 0000000 0000000 00000001066 13263421677 0017030 0 ustar 00root root 0000000 0000000 assertInstanceOf('React\Http\Io\HttpBodyStream', $response->getBody());
}
public function testStringBodyWillBePsr7Stream()
{
$response = new Response(200, array(), 'hello');
$this->assertInstanceOf('RingCentral\Psr7\Stream', $response->getBody());
}
}
http-0.8.3/tests/ServerTest.php 0000664 0000000 0000000 00000013416 13263421677 0016502 0 ustar 00root root 0000000 0000000 connection = $this->getMockBuilder('React\Socket\Connection')
->disableOriginalConstructor()
->setMethods(
array(
'write',
'end',
'close',
'pause',
'resume',
'isReadable',
'isWritable',
'getRemoteAddress',
'getLocalAddress',
'pipe'
)
)
->getMock();
$this->connection->method('isWritable')->willReturn(true);
$this->connection->method('isReadable')->willReturn(true);
$this->socket = new SocketServerStub();
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidCallbackFunctionLeadsToException()
{
new Server('invalid');
}
public function testSimpleRequestCallsRequestHandlerOnce()
{
$called = null;
$server = new Server(function (ServerRequestInterface $request) use (&$called) {
++$called;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$this->connection->emit('data', array("GET / HTTP/1.0\r\n\r\n"));
$this->assertSame(1, $called);
}
/**
* @requires PHP 5.4
*/
public function testSimpleRequestCallsArrayRequestHandlerOnce()
{
$this->called = null;
$server = new Server(array($this, 'helperCallableOnce'));
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$this->connection->emit('data', array("GET / HTTP/1.0\r\n\r\n"));
$this->assertSame(1, $this->called);
}
public function helperCallableOnce()
{
++$this->called;
}
public function testSimpleRequestWithMiddlewareArrayProcessesMiddlewareStack()
{
$called = null;
$server = new Server(array(
function (ServerRequestInterface $request, $next) use (&$called) {
$called = 'before';
$ret = $next($request->withHeader('Demo', 'ok'));
$called .= 'after';
return $ret;
},
function (ServerRequestInterface $request) use (&$called) {
$called .= $request->getHeaderLine('Demo');
}
));
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$this->connection->emit('data', array("GET / HTTP/1.0\r\n\r\n"));
$this->assertSame('beforeokafter', $called);
}
public function testPostFileUpload()
{
$loop = Factory::create();
$deferred = new Deferred();
$server = new Server(function (ServerRequestInterface $request) use ($deferred) {
$deferred->resolve($request);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$connection = $this->connection;
$data = $this->createPostFileUploadRequest();
$loop->addPeriodicTimer(0.01, function ($timer) use ($loop, &$data, $connection) {
$line = array_shift($data);
$connection->emit('data', array($line));
if (count($data) === 0) {
$loop->cancelTimer($timer);
}
});
$parsedRequest = Block\await($deferred->promise(), $loop);
$this->assertNotEmpty($parsedRequest->getUploadedFiles());
$this->assertEmpty($parsedRequest->getParsedBody());
$files = $parsedRequest->getUploadedFiles();
$this->assertTrue(isset($files['file']));
$this->assertCount(1, $files);
$this->assertSame('hello.txt', $files['file']->getClientFilename());
$this->assertSame('text/plain', $files['file']->getClientMediaType());
$this->assertSame("hello\r\n", (string)$files['file']->getStream());
}
public function testForwardErrors()
{
$exception = new \Exception();
$capturedException = null;
$server = new Server(function () use ($exception) {
return Promise\reject($exception);
});
$server->on('error', function ($error) use (&$capturedException) {
$capturedException = $error;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createPostFileUploadRequest();
$this->connection->emit('data', array(implode('', $data)));
$this->assertInstanceOf('RuntimeException', $capturedException);
$this->assertInstanceOf('Exception', $capturedException->getPrevious());
$this->assertSame($exception, $capturedException->getPrevious());
}
private function createPostFileUploadRequest()
{
$boundary = "---------------------------5844729766471062541057622570";
$data = array();
$data[] = "POST / HTTP/1.1\r\n";
$data[] = "Content-Type: multipart/form-data; boundary=" . $boundary . "\r\n";
$data[] = "Content-Length: 220\r\n";
$data[] = "\r\n";
$data[] = "--$boundary\r\n";
$data[] = "Content-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\n";
$data[] = "Content-type: text/plain\r\n";
$data[] = "\r\n";
$data[] = "hello\r\n";
$data[] = "\r\n";
$data[] = "--$boundary--\r\n";
return $data;
}
}
http-0.8.3/tests/SocketServerStub.php 0000664 0000000 0000000 00000000673 13263421677 0017652 0 ustar 00root root 0000000 0000000 connection = $this->getMockBuilder('React\Socket\Connection')
->disableOriginalConstructor()
->setMethods(
array(
'write',
'end',
'close',
'pause',
'resume',
'isReadable',
'isWritable',
'getRemoteAddress',
'getLocalAddress',
'pipe'
)
)
->getMock();
$this->connection->method('isWritable')->willReturn(true);
$this->connection->method('isReadable')->willReturn(true);
$this->socket = new SocketServerStub();
}
public function testRequestEventWillNotBeEmittedForIncompleteHeaders()
{
$server = new StreamingServer($this->expectCallableNever());
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = '';
$data .= "GET / HTTP/1.1\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestEventIsEmitted()
{
$server = new StreamingServer($this->expectCallableOnce());
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
/**
* @requires PHP 5.4
*/
public function testRequestEventIsEmittedForArrayCallable()
{
$this->called = null;
$server = new StreamingServer(array($this, 'helperCallableOnce'));
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertEquals(1, $this->called);
}
public function helperCallableOnce()
{
++$this->called;
}
public function testRequestEvent()
{
$i = 0;
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) {
$i++;
$requestAssertion = $request;
});
$this->connection
->expects($this->any())
->method('getRemoteAddress')
->willReturn('127.0.0.1:8080');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$serverParams = $requestAssertion->getServerParams();
$this->assertSame(1, $i);
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame(array(), $requestAssertion->getQueryParams());
$this->assertSame('http://example.com/', (string)$requestAssertion->getUri());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
$this->assertSame('127.0.0.1', $serverParams['REMOTE_ADDR']);
}
public function testRequestEventWithSingleRequestHandlerArray()
{
$i = 0;
$requestAssertion = null;
$server = new StreamingServer(array(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) {
$i++;
$requestAssertion = $request;
}));
$this->connection
->expects($this->any())
->method('getRemoteAddress')
->willReturn('127.0.0.1:8080');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$serverParams = $requestAssertion->getServerParams();
$this->assertSame(1, $i);
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame(array(), $requestAssertion->getQueryParams());
$this->assertSame('http://example.com/', (string)$requestAssertion->getUri());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
$this->assertSame('127.0.0.1', $serverParams['REMOTE_ADDR']);
}
public function testRequestGetWithHostAndCustomPort()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: example.com:8080\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com:8080/', (string)$requestAssertion->getUri());
$this->assertSame(8080, $requestAssertion->getUri()->getPort());
$this->assertSame('example.com:8080', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestGetWithHostAndHttpsPort()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: example.com:443\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com:443/', (string)$requestAssertion->getUri());
$this->assertSame(443, $requestAssertion->getUri()->getPort());
$this->assertSame('example.com:443', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestGetWithHostAndDefaultPortWillBeIgnored()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: example.com:80\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com/', (string)$requestAssertion->getUri());
$this->assertNull($requestAssertion->getUri()->getPort());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestOptionsAsterisk()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('OPTIONS', $requestAssertion->getMethod());
$this->assertSame('*', $requestAssertion->getRequestTarget());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com', (string)$requestAssertion->getUri());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestNonOptionsWithAsteriskRequestTargetWillReject()
{
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', $this->expectCallableOnce());
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET * HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestConnectAuthorityForm()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('CONNECT', $requestAssertion->getMethod());
$this->assertSame('example.com:443', $requestAssertion->getRequestTarget());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com:443', (string)$requestAssertion->getUri());
$this->assertSame(443, $requestAssertion->getUri()->getPort());
$this->assertSame('example.com:443', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestConnectWithoutHostWillBeAdded()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT example.com:443 HTTP/1.1\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('CONNECT', $requestAssertion->getMethod());
$this->assertSame('example.com:443', $requestAssertion->getRequestTarget());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com:443', (string)$requestAssertion->getUri());
$this->assertSame(443, $requestAssertion->getUri()->getPort());
$this->assertSame('example.com:443', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestConnectAuthorityFormWithDefaultPortWillBeIgnored()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('CONNECT', $requestAssertion->getMethod());
$this->assertSame('example.com:80', $requestAssertion->getRequestTarget());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com', (string)$requestAssertion->getUri());
$this->assertNull($requestAssertion->getUri()->getPort());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestConnectAuthorityFormNonMatchingHostWillBeOverwritten()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT example.com:80 HTTP/1.1\r\nHost: other.example.org\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('CONNECT', $requestAssertion->getMethod());
$this->assertSame('example.com:80', $requestAssertion->getRequestTarget());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com', (string)$requestAssertion->getUri());
$this->assertNull($requestAssertion->getUri()->getPort());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestConnectOriginFormRequestTargetWillReject()
{
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', $this->expectCallableOnce());
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT / HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestNonConnectWithAuthorityRequestTargetWillReject()
{
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', $this->expectCallableOnce());
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET example.com:80 HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestWithoutHostEventUsesSocketAddress()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$this->connection
->expects($this->any())
->method('getLocalAddress')
->willReturn('127.0.0.1:80');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET /test HTTP/1.0\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/test', $requestAssertion->getRequestTarget());
$this->assertEquals('http://127.0.0.1/test', $requestAssertion->getUri());
$this->assertSame('/test', $requestAssertion->getUri()->getPath());
}
public function testRequestAbsoluteEvent()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET http://example.com/test HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('http://example.com/test', $requestAssertion->getRequestTarget());
$this->assertEquals('http://example.com/test', $requestAssertion->getUri());
$this->assertSame('/test', $requestAssertion->getUri()->getPath());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestAbsoluteAddsMissingHostEvent()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET http://example.com:8080/test HTTP/1.0\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('http://example.com:8080/test', $requestAssertion->getRequestTarget());
$this->assertEquals('http://example.com:8080/test', $requestAssertion->getUri());
$this->assertSame('/test', $requestAssertion->getUri()->getPath());
$this->assertSame('example.com:8080', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestAbsoluteNonMatchingHostWillBeOverwritten()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET http://example.com/test HTTP/1.1\r\nHost: other.example.org\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('http://example.com/test', $requestAssertion->getRequestTarget());
$this->assertEquals('http://example.com/test', $requestAssertion->getUri());
$this->assertSame('/test', $requestAssertion->getUri()->getPath());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestOptionsAsteriskEvent()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('OPTIONS', $requestAssertion->getMethod());
$this->assertSame('*', $requestAssertion->getRequestTarget());
$this->assertEquals('http://example.com', $requestAssertion->getUri());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestOptionsAbsoluteEvent()
{
$requestAssertion = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestAssertion) {
$requestAssertion = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "OPTIONS http://example.com HTTP/1.1\r\nHost: example.com\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('OPTIONS', $requestAssertion->getMethod());
$this->assertSame('http://example.com', $requestAssertion->getRequestTarget());
$this->assertEquals('http://example.com', $requestAssertion->getUri());
$this->assertSame('', $requestAssertion->getUri()->getPath());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
}
public function testRequestPauseWillBeForwardedToConnection()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
$request->getBody()->pause();
});
$this->connection->expects($this->once())->method('pause');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestResumeWillBeForwardedToConnection()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
$request->getBody()->resume();
});
$this->connection->expects($this->once())->method('resume');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestCloseWillNotCloseConnection()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
$request->getBody()->close();
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestPauseAfterCloseWillNotBeForwarded()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
$request->getBody()->close();
$request->getBody()->pause();
});
$this->connection->expects($this->never())->method('close');
$this->connection->expects($this->never())->method('pause');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestResumeAfterCloseWillNotBeForwarded()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
$request->getBody()->close();
$request->getBody()->resume();
});
$this->connection->expects($this->never())->method('close');
$this->connection->expects($this->never())->method('resume');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestEventWithoutBodyWillNotEmitData()
{
$never = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($never) {
$request->getBody()->on('data', $never);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestEventWithSecondDataEventWillEmitBodyData()
{
$once = $this->expectCallableOnceWith('incomplete');
$server = new StreamingServer(function (ServerRequestInterface $request) use ($once) {
$request->getBody()->on('data', $once);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = '';
$data .= "POST / HTTP/1.1\r\n";
$data .= "Host: localhost\r\n";
$data .= "Content-Length: 100\r\n";
$data .= "\r\n";
$data .= "incomplete";
$this->connection->emit('data', array($data));
}
public function testRequestEventWithPartialBodyWillEmitData()
{
$once = $this->expectCallableOnceWith('incomplete');
$server = new StreamingServer(function (ServerRequestInterface $request) use ($once) {
$request->getBody()->on('data', $once);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = '';
$data .= "POST / HTTP/1.1\r\n";
$data .= "Host: localhost\r\n";
$data .= "Content-Length: 100\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$data = '';
$data .= "incomplete";
$this->connection->emit('data', array($data));
}
public function testResponseContainsPoweredByHeader()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response();
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("\r\nX-Powered-By: React/alpha\r\n", $buffer);
}
public function testResponsePendingPromiseWillNotSendAnything()
{
$never = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($never) {
return new Promise(function () { }, $never);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertEquals('', $buffer);
}
public function testResponsePendingPromiseWillBeCancelledIfConnectionCloses()
{
$once = $this->expectCallableOnce();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($once) {
return new Promise(function () { }, $once);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->connection->emit('close');
$this->assertEquals('', $buffer);
}
public function testRespomseBodyStreamAlreadyClosedWillSendEmptyBodyChunkedEncoded()
{
$stream = new ThroughStream();
$stream->close();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertStringEndsWith("\r\n\r\n0\r\n\r\n", $buffer);
}
public function testResponseBodyStreamEndingWillSendEmptyBodyChunkedEncoded()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$stream->end();
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertStringEndsWith("\r\n\r\n0\r\n\r\n", $buffer);
}
public function testResponseBodyStreamAlreadyClosedWillSendEmptyBodyPlainHttp10()
{
$stream = new ThroughStream();
$stream->close();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertStringStartsWith("HTTP/1.0 200 OK\r\n", $buffer);
$this->assertStringEndsWith("\r\n\r\n", $buffer);
}
public function testResponseStreamWillBeClosedIfConnectionIsAlreadyClosed()
{
$stream = new ThroughStream();
$stream->on('close', $this->expectCallableOnce());
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$this->connection = $this->getMockBuilder('React\Socket\Connection')
->disableOriginalConstructor()
->setMethods(
array(
'write',
'end',
'close',
'pause',
'resume',
'isReadable',
'isWritable',
'getRemoteAddress',
'getLocalAddress',
'pipe'
)
)
->getMock();
$this->connection->expects($this->once())->method('isWritable')->willReturn(false);
$this->connection->expects($this->never())->method('write');
$this->connection->expects($this->never())->method('write');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testResponseBodyStreamWillBeClosedIfConnectionEmitsCloseEvent()
{
$stream = new ThroughStream();
$stream->on('close', $this->expectCallableOnce());
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->connection->emit('close');
}
public function testResponseUpgradeInResponseCanBeUsedToAdvertisePossibleUpgrade()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'date' => '',
'x-powered-by' => '',
'Upgrade' => 'demo'
),
'foo'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals("HTTP/1.1 200 OK\r\nUpgrade: demo\r\nContent-Length: 3\r\nConnection: close\r\n\r\nfoo", $buffer);
}
public function testResponseUpgradeWishInRequestCanBeIgnoredByReturningNormalResponse()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'date' => '',
'x-powered-by' => ''
),
'foo'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nUpgrade: demo\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 3\r\nConnection: close\r\n\r\nfoo", $buffer);
}
public function testResponseUpgradeSwitchingProtocolIncludesConnectionUpgradeHeaderWithoutContentLength()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
101,
array(
'date' => '',
'x-powered-by' => '',
'Upgrade' => 'demo'
),
'foo'
);
});
$server->on('error', 'printf');
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nUpgrade: demo\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals("HTTP/1.1 101 Switching Protocols\r\nUpgrade: demo\r\nConnection: upgrade\r\n\r\nfoo", $buffer);
}
public function testResponseUpgradeSwitchingProtocolWithStreamWillPipeDataToConnection()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
101,
array(
'date' => '',
'x-powered-by' => '',
'Upgrade' => 'demo'
),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nUpgrade: demo\r\n\r\n";
$this->connection->emit('data', array($data));
$stream->write('hello');
$stream->write('world');
$this->assertEquals("HTTP/1.1 101 Switching Protocols\r\nUpgrade: demo\r\nConnection: upgrade\r\n\r\nhelloworld", $buffer);
}
public function testResponseConnectMethodStreamWillPipeDataToConnection()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n";
$this->connection->emit('data', array($data));
$stream->write('hello');
$stream->write('world');
$this->assertStringEndsWith("\r\n\r\nhelloworld", $buffer);
}
public function testResponseConnectMethodStreamWillPipeDataFromConnection()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$this->connection->expects($this->once())->method('pipe')->with($stream);
$data = "CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testResponseContainsSameRequestProtocolVersionAndChunkedBodyForHttp11()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(),
'bye'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("bye", $buffer);
}
public function testResponseContainsSameRequestProtocolVersionAndRawBodyForHttp10()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(),
'bye'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.0\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.0 200 OK\r\n", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
$this->assertContains("bye", $buffer);
}
public function testResponseContainsNoResponseBodyForHeadRequest()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(),
'bye'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertNotContains("bye", $buffer);
}
public function testResponseContainsNoResponseBodyAndNoContentLengthForNoContentStatus()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
204,
array(),
'bye'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 204 No Content\r\n", $buffer);
$this->assertNotContains("\r\n\Content-Length: 3\r\n", $buffer);
$this->assertNotContains("bye", $buffer);
}
public function testResponseContainsNoResponseBodyForNotModifiedStatus()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
304,
array(),
'bye'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 304 Not Modified\r\n", $buffer);
$this->assertContains("\r\nContent-Length: 3\r\n", $buffer);
$this->assertNotContains("bye", $buffer);
}
public function testRequestInvalidHttpProtocolVersionWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.2\r\nHost: localhost\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertContains("HTTP/1.1 505 HTTP Version not supported\r\n", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
$this->assertContains("Error 505: HTTP Version not supported", $buffer);
}
public function testRequestOverflowWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\nX-DATA: ";
$data .= str_repeat('A', 8193 - strlen($data)) . "\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('OverflowException', $error);
$this->assertContains("HTTP/1.1 431 Request Header Fields Too Large\r\n", $buffer);
$this->assertContains("\r\n\r\nError 431: Request Header Fields Too Large", $buffer);
}
public function testRequestInvalidWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "bad request\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}
public function testRequestContentLengthBodyDataWillEmitDataEventOnRequestStream()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
}
public function testRequestChunkedTransferEncodingRequestWillEmitDecodedDataEventOnRequestStream()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$data .= "0\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals('chunked', $requestValidation->getHeaderLine('Transfer-Encoding'));
}
public function testRequestChunkedTransferEncodingWithAdditionalDataWontBeEmitted()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$data .= "0\r\n\r\n";
$data .= "2\r\nhi\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestChunkedTransferEncodingEmpty()
{
$dataEvent = $this->expectCallableNever();
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "0\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestChunkedTransferEncodingHeaderCanBeUpperCase()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: CHUNKED\r\n";
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$data .= "0\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals('CHUNKED', $requestValidation->getHeaderLine('Transfer-Encoding'));
}
public function testRequestChunkedTransferEncodingCanBeMixedUpperAndLowerCase()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: CHunKeD\r\n";
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$data .= "0\r\n\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestWithMalformedHostWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: ///\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}
public function testRequestWithInvalidHostUriComponentsWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\nHost: localhost:80/test\r\n\r\n";
$this->connection->emit('data', array($data));
$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}
public function testRequestContentLengthWillEmitDataEventAndEndEventAndAdditionalDataWillBeIgnored()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
return \React\Promise\resolve(new Response());
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5\r\n";
$data .= "\r\n";
$data .= "hello";
$data .= "world";
$this->connection->emit('data', array($data));
}
public function testRequestContentLengthWillEmitDataEventAndEndEventAndAdditionalDataWillBeIgnoredSplitted()
{
$dataEvent = $this->expectCallableOnceWith('hello');
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
$data = "world";
$this->connection->emit('data', array($data));
}
public function testRequestZeroContentLengthWillEmitEndEvent()
{
$dataEvent = $this->expectCallableNever();
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 0\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestZeroContentLengthWillEmitEndAndAdditionalDataWillBeIgnored()
{
$dataEvent = $this->expectCallableNever();
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 0\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
}
public function testRequestZeroContentLengthWillEmitEndAndAdditionalDataWillBeIgnoredSplitted()
{
$dataEvent = $this->expectCallableNever();
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 0\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$data = "hello";
$this->connection->emit('data', array($data));
}
public function testRequestWithBothContentLengthAndTransferEncodingWillEmitServerErrorAndSendResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testRequestInvalidNonIntegerContentLengthWillEmitServerErrorAndSendResponse()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: bla\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testRequestInvalidHeadRequestWithInvalidNonIntegerContentLengthWillEmitServerErrorAndSendResponseWithoutBody()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "HEAD / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: bla\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertNotContains("\r\n\r\nError 400: Bad Request", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testRequestInvalidMultipleContentLengthWillEmitErrorOnServer()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
$error = $message;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 5, 3, 4\r\n";
$data .= "\r\n";
$data .= "hello";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testRequestInvalidChunkHeaderTooLongWillEmitErrorOnRequestStream()
{
$errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception'));
$server = new StreamingServer(function ($request) use ($errorEvent){
$request->getBody()->on('error', $errorEvent);
return \React\Promise\resolve(new Response());
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
for ($i = 0; $i < 1025; $i++) {
$data .= 'a';
}
$this->connection->emit('data', array($data));
}
public function testRequestInvalidChunkBodyTooLongWillEmitErrorOnRequestStream()
{
$errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception'));
$server = new StreamingServer(function ($request) use ($errorEvent){
$request->getBody()->on('error', $errorEvent);
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "5\r\nhello world\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestUnexpectedEndOfRequestWithChunkedTransferConnectionWillEmitErrorOnRequestStream()
{
$errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception'));
$server = new StreamingServer(function ($request) use ($errorEvent){
$request->getBody()->on('error', $errorEvent);
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$this->connection->emit('data', array($data));
$this->connection->emit('end');
}
public function testRequestInvalidChunkHeaderWillEmitErrorOnRequestStream()
{
$errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception'));
$server = new StreamingServer(function ($request) use ($errorEvent){
$request->getBody()->on('error', $errorEvent);
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: chunked\r\n";
$data .= "\r\n";
$data .= "hello\r\nhello\r\n";
$this->connection->emit('data', array($data));
}
public function testRequestUnexpectedEndOfRequestWithContentLengthWillEmitErrorOnRequestStream()
{
$errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception'));
$server = new StreamingServer(function ($request) use ($errorEvent){
$request->getBody()->on('error', $errorEvent);
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Content-Length: 500\r\n";
$data .= "\r\n";
$data .= "incomplete";
$this->connection->emit('data', array($data));
$this->connection->emit('end');
}
public function testRequestWithoutBodyWillEmitEndOnRequestStream()
{
$dataEvent = $this->expectCallableNever();
$closeEvent = $this->expectCallableOnce();
$endEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function ($request) use ($dataEvent, $closeEvent, $endEvent, $errorEvent){
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('error', $errorEvent);
});
$this->connection->expects($this->never())->method('close');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
}
public function testRequestWithoutDefinedLengthWillIgnoreDataEvent()
{
$dataEvent = $this->expectCallableNever();
$endEvent = $this->expectCallableOnce();
$closeEvent = $this->expectCallableOnce();
$errorEvent = $this->expectCallableNever();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) {
$request->getBody()->on('data', $dataEvent);
$request->getBody()->on('end', $endEvent);
$request->getBody()->on('close', $closeEvent);
$request->getBody()->on('error', $errorEvent);
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$data .= "hello world";
$this->connection->emit('data', array($data));
}
public function testResponseWithBodyStreamWillUseChunkedTransferEncodingByDefault()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$stream->emit('data', array('hello'));
$this->assertContains("Transfer-Encoding: chunked", $buffer);
$this->assertContains("hello", $buffer);
}
public function testResponseWithBodyStringWillOverwriteExplicitContentLengthAndTransferEncoding()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Content-Length' => 1000,
'Transfer-Encoding' => 'chunked'
),
'hello'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertNotContains("Transfer-Encoding: chunked", $buffer);
$this->assertContains("Content-Length: 5", $buffer);
$this->assertContains("hello", $buffer);
}
public function testResponseWithCustomTransferEncodingWillBeIgnoredAndUseChunkedTransferEncodingInstead()
{
$stream = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($stream) {
return new Response(
200,
array(
'Transfer-Encoding' => 'custom'
),
$stream
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$stream->emit('data', array('hello'));
$this->assertContains('Transfer-Encoding: chunked', $buffer);
$this->assertNotContains('Transfer-Encoding: custom', $buffer);
$this->assertContains("5\r\nhello\r\n", $buffer);
}
public function testResponseWithoutExplicitDateHeaderWillAddCurrentDate()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response();
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("Date:", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
}
public function testResponseWIthCustomDateHeaderOverwritesDefault()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array("Date" => "Tue, 15 Nov 1994 08:12:31 GMT")
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
}
public function testResponseWithEmptyDateHeaderRemovesDateHeader()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array('Date' => '')
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertNotContains("Date:", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
}
public function testResponseCanContainMultipleCookieHeaders()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array(
'Set-Cookie' => array(
'name=test',
'session=abc'
),
'Date' => '',
'X-Powered-By' => ''
)
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertEquals("HTTP/1.1 200 OK\r\nSet-Cookie: name=test\r\nSet-Cookie: session=abc\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", $buffer);
}
public function testRequestOnlyChunkedEncodingIsAllowedForTransferEncoding()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($exception) use (&$error) {
$error = $exception;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Transfer-Encoding: custom\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 501 Not Implemented\r\n", $buffer);
$this->assertContains("\r\n\r\nError 501: Not Implemented", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testRequestOnlyChunkedEncodingIsAllowedForTransferEncodingWithHttp10()
{
$error = null;
$server = new StreamingServer($this->expectCallableNever());
$server->on('error', function ($exception) use (&$error) {
$error = $exception;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.0\r\n";
$data .= "Transfer-Encoding: custom\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.0 501 Not Implemented\r\n", $buffer);
$this->assertContains("\r\n\r\nError 501: Not Implemented", $buffer);
$this->assertInstanceOf('InvalidArgumentException', $error);
}
public function testReponseWithExpectContinueRequestContainsContinueWithLaterResponse()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response();
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Expect: 100-continue\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 100 Continue\r\n", $buffer);
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
}
public function testResponseWithExpectContinueRequestWontSendContinueForHttp10()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response();
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.0\r\n";
$data .= "Expect: 100-continue\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.0 200 OK\r\n", $buffer);
$this->assertNotContains("HTTP/1.1 100 Continue\r\n\r\n", $buffer);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidCallbackFunctionLeadsToException()
{
$server = new StreamingServer('invalid');
}
public function testResponseBodyStreamWillStreamDataWithChunkedTransferEncoding()
{
$input = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($input) {
return new Response(
200,
array(),
$input
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$input->emit('data', array('1'));
$input->emit('data', array('23'));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
$this->assertContains("1\r\n1\r\n", $buffer);
$this->assertContains("2\r\n23\r\n", $buffer);
}
public function testResponseBodyStreamWithContentLengthWillStreamTillLengthWithoutTransferEncoding()
{
$input = new ThroughStream();
$server = new StreamingServer(function (ServerRequestInterface $request) use ($input) {
return new Response(
200,
array('Content-Length' => 5),
$input
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$input->emit('data', array('hel'));
$input->emit('data', array('lo'));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("Content-Length: 5\r\n", $buffer);
$this->assertNotContains("Transfer-Encoding", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
$this->assertContains("hello", $buffer);
}
public function testResponseWithResponsePromise()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return \React\Promise\resolve(new Response());
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("\r\n\r\n", $buffer);
}
public function testResponseReturnInvalidTypeWillResultInError()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return "invalid";
});
$exception = null;
$server->on('error', function (\Exception $ex) use (&$exception) {
$exception = $ex;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
$this->assertInstanceOf('RuntimeException', $exception);
}
public function testResponseResolveWrongTypeInPromiseWillResultInError()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return \React\Promise\resolve("invalid");
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
}
public function testResponseRejectedPromiseWillResultInErrorMessage()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Promise(function ($resolve, $reject) {
$reject(new \Exception());
});
});
$server->on('error', $this->expectCallableOnce());
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
}
public function testResponseExceptionInCallbackWillResultInErrorMessage()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Promise(function ($resolve, $reject) {
throw new \Exception('Bad call');
});
});
$server->on('error', $this->expectCallableOnce());
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
}
public function testResponseWithContentLengthHeaderForStringBodyOverwritesTransferEncoding()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response(
200,
array('Transfer-Encoding' => 'chunked'),
'hello'
);
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
$this->assertContains("Content-Length: 5\r\n", $buffer);
$this->assertContains("hello", $buffer);
$this->assertNotContains("Transfer-Encoding", $buffer);
}
public function testResponseWillBeHandled()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Response();
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
}
public function testResponseExceptionThrowInCallBackFunctionWillResultInErrorMessage()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
throw new \Exception('hello');
});
$exception = null;
$server->on('error', function (\Exception $ex) use (&$exception) {
$exception = $ex;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertInstanceOf('RuntimeException', $exception);
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
$this->assertEquals('hello', $exception->getPrevious()->getMessage());
}
/**
* @requires PHP 7
*/
public function testResponseThrowableThrowInCallBackFunctionWillResultInErrorMessage()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
throw new \Error('hello');
});
$exception = null;
$server->on('error', function (\Exception $ex) use (&$exception) {
$exception = $ex;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
try {
$this->connection->emit('data', array($data));
} catch (\Error $e) {
$this->markTestSkipped(
'A \Throwable bubbled out of the request callback. ' .
'This happened most probably due to react/promise:^1.0 being installed ' .
'which does not support \Throwable.'
);
}
$this->assertInstanceOf('RuntimeException', $exception);
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
$this->assertEquals('hello', $exception->getPrevious()->getMessage());
}
public function testResponseRejectOfNonExceptionWillResultInErrorMessage()
{
$server = new StreamingServer(function (ServerRequestInterface $request) {
return new Promise(function ($resolve, $reject) {
$reject('Invalid type');
});
});
$exception = null;
$server->on('error', function (\Exception $ex) use (&$exception) {
$exception = $ex;
});
$buffer = '';
$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$this->assertContains("HTTP/1.1 500 Internal Server Error\r\n", $buffer);
$this->assertInstanceOf('RuntimeException', $exception);
}
public function testRequestServerRequestParams()
{
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
});
$this->connection
->expects($this->any())
->method('getRemoteAddress')
->willReturn('192.168.1.2:80');
$this->connection
->expects($this->any())
->method('getLocalAddress')
->willReturn('127.0.0.1:8080');
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = $this->createGetRequest();
$this->connection->emit('data', array($data));
$serverParams = $requestValidation->getServerParams();
$this->assertEquals('127.0.0.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8080', $serverParams['SERVER_PORT']);
$this->assertEquals('192.168.1.2', $serverParams['REMOTE_ADDR']);
$this->assertEquals('80', $serverParams['REMOTE_PORT']);
$this->assertNotNull($serverParams['REQUEST_TIME']);
$this->assertNotNull($serverParams['REQUEST_TIME_FLOAT']);
}
public function testRequestQueryParametersWillBeAddedToRequest()
{
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET /foo.php?hello=world&test=bar HTTP/1.0\r\n\r\n";
$this->connection->emit('data', array($data));
$queryParams = $requestValidation->getQueryParams();
$this->assertEquals('world', $queryParams['hello']);
$this->assertEquals('bar', $queryParams['test']);
}
public function testRequestCookieWillBeAddedToServerRequest()
{
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Cookie: hello=world\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals(array('hello' => 'world'), $requestValidation->getCookieParams());
}
public function testRequestInvalidMultipleCookiesWontBeAddedToServerRequest()
{
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Cookie: hello=world\r\n";
$data .= "Cookie: test=failed\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals(array(), $requestValidation->getCookieParams());
}
public function testRequestCookieWithSeparatorWillBeAddedToServerRequest()
{
$requestValidation = null;
$server = new StreamingServer(function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
});
$server->listen($this->socket);
$this->socket->emit('connection', array($this->connection));
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "Cookie: hello=world; test=abc\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
$this->assertEquals(array('hello' => 'world', 'test' => 'abc'), $requestValidation->getCookieParams());
}
private function createGetRequest()
{
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Connection: close\r\n";
$data .= "\r\n";
return $data;
}
}
http-0.8.3/tests/TestCase.php 0000664 0000000 0000000 00000003023 13263421677 0016100 0 ustar 00root root 0000000 0000000 createCallableMock();
$mock
->expects($this->exactly($amount))
->method('__invoke');
return $mock;
}
protected function expectCallableOnce()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke');
return $mock;
}
protected function expectCallableOnceWith($value)
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($value);
return $mock;
}
protected function expectCallableNever()
{
$mock = $this->createCallableMock();
$mock
->expects($this->never())
->method('__invoke');
return $mock;
}
protected function expectCallableConsecutive($numberOfCalls, array $with)
{
$mock = $this->createCallableMock();
for ($i = 0; $i < $numberOfCalls; $i++) {
$mock
->expects($this->at($i))
->method('__invoke')
->with($this->equalTo($with[$i]));
}
return $mock;
}
protected function createCallableMock()
{
return $this
->getMockBuilder('React\Tests\Http\CallableStub')
->getMock();
}
}
http-0.8.3/tests/benchmark-middleware-runner.php 0000664 0000000 0000000 00000001737 13263421677 0021753 0 ustar 00root root 0000000 0000000 addPsr4('React\\Tests\\Http\\', __DIR__);