laminas / laminas-diactoros Goto Github PK
View Code? Open in Web Editor NEWPSR HTTP Message implementations
Home Page: https://docs.laminas.dev/laminas-diactoros/
License: BSD 3-Clause "New" or "Revised" License
PSR HTTP Message implementations
Home Page: https://docs.laminas.dev/laminas-diactoros/
License: BSD 3-Clause "New" or "Revised" License
Q | A |
---|---|
Version(s) | 2.2.2 |
Hi,
I use the UploadedFile object to make my PHPUnit test cases more realistic for my application. (See example below) After an update of the
laminas-diactoros framework, I noticed that many PHPUnit test cases no longer work. This is because the UploadedFile object deletes the original file after use. See commit link: 197f195#commitcomment-77930965
If this is not a bug, is there an alternative to creating an UploadedFile object without the object deleting the file?
Danger! This only applies if multiple PHPUnit test cases that want to use the same file to test a function.
public function testAddAuthorizedPostDataOKActiveTrue(): void {
$data = array(
'active' => true,
'uploadFiles' => array(new UploadedFile( TESTS . '/TestFiles/Home-Network.pdf', 0, UPLOAD_ERR_OK, 'Home-Network.pdf', 'application/pdf')),
);
$this->post( 'documents/add', $data );
$this->assertFlashMessage( 'The document could be saved successfully', 'flash' );
}
public function testAddAuthorizedPostDataOKActiveFalse(): void {
$data = array(
'active' => false,
'uploadFiles' => array(new UploadedFile( TESTS . '/TestFiles/Home-Network.pdf', 0, UPLOAD_ERR_OK, 'Home-Network.pdf', 'application/pdf')),
);
$this->post( 'documents/add', $data );
$this->assertFlashMessage( 'The document could be saved successfully', 'flash' );
}
Not sure what is going on? Could it be http2 related?
generated by: It's comes Charles's "Copy Curl Request" feature
curl -H ':method: POST' -H ':scheme: https' -H ':path: /identity/update-display-name' -H ':authority: api-development.plhw.nl' -H 'accept: application/json, text/javascript, */*; q=0.01' -H 'content-type: application/json; charset=UTF-8' -H 'origin: http://localhost:4200' -H 'accept-encoding: gzip, deflate' -H 'content-length: 72' -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7' -H 'referer: http://localhost:4200/account/settings' -H 'dnt: 1' -H 'accept-language: en-us' --data-binary '{"user_id":"6fc88c51-c16a-4612-9b8b-e48e15931689","display_name":"BasK"}' 'https://api-development.plhw.nl/identity/update-display-name' | gunzip
Errors with:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 479 100 407 100 72 970 171 --:--:-- --:--:-- --:--:-- 973
<br />
<b>Fatal error</b>: Uncaught InvalidArgumentException: "" is not valid header name in /home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/HeaderSecurity.php:150
Stack trace:
#0 /home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/RequestTrait.php(329): Zend\Diactoros\HeaderSecurity::assertValidName('')
#1 /home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/RequestTrait.php(70): Zend\Diactoros\ServerRequest->assertHeaders(Array)
#2 /home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/ServerRequest.php(96): Zend\Diactoros\ServerRequest->initialize(Object(Zend\Diactoros\Uri), 'POST', Object(Zend\Diactoros\PhpInputStream), Array)
#3 /home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/ServerRequestFactory.php(73): Zend\Diactoros\ServerRequest->__const in <b>/home-projects/api-plhw-development/deploy/releases/20160529202409UTC/vendor/zendframework/zend-diactoros/src/HeaderSecurity.php</b> on line <b>150</b><br />
Originally posted by @basz at zendframework/zend-diactoros#198
I was looking through stream implementation. Relative stream and the stream it wraps are sharing same pointer, right?
So if we rewind wrapped stream, read on relative will return data before offset position.
Also tell() will give negative position, which is out of bounds error.
This shared pointer in streams looks extremely bug prone to me.
May be on seekable streams we should store pointer locally, set it before performing operation and record new position after doing it?
As simplified example:
public function read()
{
$this->seek($this->position, SEEK_SET);
$result = fread($resource, $length);
$this->position = $this->tell();
return $result;
}
Originally posted by @Xerkus at zendframework/zend-diactoros#147
This all works fine. Great.
But why all the logic is concentrated in one place?
Moreover, there is a class of Server.
I think it would be better to make a directory of the factories.
Firstly, it allows to separate each logic element in its factory.
Secondly, because I can always get in the code of the original values.
For example, Zend\Diactoros\Factory\HttpProtocolFactory:
use UnexpectedValueException;
class HttpProtocolFactory
{
public static function make(array $server = null)
{
if (empty($server)) {
$server = $_SERVER;
}
if (! isset($server['SERVER_PROTOCOL'])) {
return '1.1';
}
$protocol = $server['SERVER_PROTOCOL'];
if (! preg_match('#\A(?:HTTP/)?(?P<version>\d{1}\.\d+)\Z#', $protocol, $matches)) {
throw new UnexpectedValueException(sprintf(
'Unrecognized protocol version "%s".',
$server['SERVER_PROTOCOL']
));
}
return $matches['version'];
}
}
All this is true for the request method, headers, Uri, uploaded files etc
Originally posted by @easy-system at zendframework/zend-diactoros#158
Q | A |
---|---|
Version(s) | 1.8.7p2 |
zend-diactoros and laminas-diactoros define the same function in the same namespace. This can lead to PHP fatal errors when both are installed in the same project. It seems like either this conflict should be resolved (i.e. by fixing the namespace or function name), or laminas-diactoros should formally declare a conflict with the older zend-diactoros in composer.json.
When running PHPUnit on our own downstream project, an error is thrown:
PHP Fatal error: Cannot redeclare Zend\Diactoros\createUploadedFile() (previously declared in /home/travis/build/acquia/blt/vendor/zendframework/zend-diactoros/src/functions/create_uploaded_file.php:20) in /tmp/blt-sandbox-instance/vendor/laminas/laminas-diactoros/src/functions/create_uploaded_file.legacy.php on line 19
I haven't figured out a more minimal way to reproduce this yet, but you can see it failing in our tests above.
laminas-diactoros and zend-diactoros should be able to be installed side-by-side without conflict, or there should be a formal Composer dependency preventing them from being installed simultaneously.
Q | A |
---|---|
New Feature | yes |
RFC | kinda |
BC Break | no |
I've seen there was already such a feature request in #48 which was somehow related to PSR-7 Streams which do actually represent a multipart payload.
From what I can see, php-http/multipart-stream-builder is the only library which does actually provide this functionality but I wonder if this should also be possible via diactoros itself.
Something like this came to my mind:
interface MultipartStreamInterface extends StreamInterface
{
/** @return non-empty-string */
public function getBoundary(): string;
}
interface PartOfMultipartStreamInterface
{
/**
* Returns the filename of the multipart stream.
* In case no filename was provided, this method returns an empty string.
*/
public function getFilename(): string;
/**
* Returns all headers which are passed for the stream.
* All header names are converted to lowercase.
* @return array<non-empty-string,non-empty-string>
*/
public function getHeaders(): array;
public function getStream(): StreamInterface;
}
interface MultipartStreamFactoryInterface extends StreamFactoryInterface
{
/** @param non-empty-string $boundary */
public function createMultipartStream(string $boundary, PartOfMultipartStreamInterface ...$parts): MultipartStreamInterface;
/**
* @param array<non-empty-string,non-empty-string> $headers
*/
public function createPartOfMultipart(StreamInterface $stream, string $name, string $filename = '', array $headers = []): PartOfMultipartStreamInterface;
}
I guess this API should provide anything we need to generate multipart streams, right?
Happy to get some feedback here.
Q | A |
---|---|
New Feature | yes |
RFC | ? |
BC Break | no |
I would like to be able to use ResponseFactory
to create instances of the various *Response
classes.
Q | A |
---|---|
New Feature | yes |
To be prepared for the december release of PHP 8.0, this repository has some additional TODOs to be tested against the new major version.
In order to make this repository compatible, one has to follow these steps:
composer.json
to provide support for PHP 8.0 by adding the constraint ~8.0.0
composer.json
to drop support for PHP less than 7.3composer.json
to implement phpunit 9.3 which supports PHP 7.3+.travis.yml
to ignore platform requirements when installing composer dependencies (simply add --ignore-platform-reqs
to COMPOSER_ARGS
env variable).travis.yml
to add PHP 8.0 to the matrix (NOTE: Do not allow failures as PHP 8.0 has a feature freeze since 2020-08-04!)According to https://framework.zend.com/long-term-support 1.7 is the long term support version of zend-diactoros, however the Symfony security scanner shows that 1.7.2 is missing the fix for the URL Rewrite vulnerability [CVE-NONE-0001]: https://framework.zend.com/security/advisory/ZF2018-01
I've tried to be helpful and backport this in https://github.com/alexpott/zend-diactoros/tree/1.7.x-CVE-NONE-0001 but I can't create a PR because there is no 1.7 release branch.
$server = [
'REQUEST_URI' => 'https://example.com/requested/path',
'HTTP_X_ORIGINAL_URL' => '/hijack-attempt'
];
$path = ServerRequestFactory::marshalRequestUri($server);
$path === '/requested/path';
$path === '/hijack-attempt';
Originally posted by @alexpott at zendframework/zend-diactoros#373
Q | A |
---|---|
Version(s) | 2.5.0 |
Laminas\Diactoros\Exception\InvalidArgumentException: Unsupported HTTP protocol version "2.0" provided
exception when enabling http2 on nginx as it passes a protocol of HTTP/2.0
Enable http2 on nginx (Current version tested is 1.21.1)
No error with HTTP/2.0
What must return Stream::getMetadata() if resource is detached?
P.S.:
What should be the level of expectation of the error?
if i use:
$stream->write(function () {});
Do I have to expects an RutimeException?
Originally posted by @easy-system at zendframework/zend-diactoros#168
I'm finishing coding League\Url v4 and it exposed the following class in public API
Url::sameValueAs(Psr\Http\Message\UriInterface $url);
To compare two PSR-7 UriInterface object based on their __toString
method output.
I tried to make it work with Diactoros but I failed with the following edge cases URLs:
http:/example.com
http:example.com
In both cases the object is instantiated and the respective Uri components are correctly set but the __toString
method adds extra path and/or authority delimiters.
Below the code to reproduce the bugs:
use Zend\Diactoros\Uri;
$url = new Uri('http:/example.com');
echo $url->__toString(); //expected http:/example.com but got http:///example.com
new Uri($url->__toString()); //throw InvalidArgumentException
The authority delimiters are added (not required according to PSR-7) and the method returns an invalid Uri.
use Zend\Diactoros\Uri;
$url = new Uri('http:example.com');
echo $url->__toString(); //expected http:example.com but got http:///example.com
new Uri($url->__toString()); //throw InvalidArgumentException
The authority delimiters and a path delimiter are added (not required according to PSR-7) and the method returns an invalid Uri.
Originally posted by @nyamsprod at zendframework/zend-diactoros#65
upload a image from web -> To Server A
A:
$request->ServerRequestFactory::createGlobal();
use GuzzleHttp to send this request , -> To Server B;
B:
accept $_FILE is empty
Q | A |
---|---|
New Feature | yes |
RFC | yes/no |
BC Break | yes/no |
if ($request->getBody() instanceof Psr7\MultipartStream) {
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
. $request->getBody()->getBoundary();
}
Note: This could be totally intentional, and it was for a major version, so not a problem. But wanted to make sure this was the intention. I don't think it was as the change log doesn't mention it as a breaking change, or even reference the potential issue:
zendframework/zend-diactoros#329 adds return type hints and scalar parameter type hints wherever possible. The changes were done to help improve code quality, in part by reducing manual type checking. If you are extending any classes, you may need to update your signatures; check the signatures of the class(es) you are extending for changes.
Q | A |
---|---|
Version | 2.x.x |
Because strict_types
are enabled in the library, but method arguments lack type hints (due to the interface understandably not type hinting arguments), Stream::write($string)
and other methods accept values that can be juggled into the expected type (and were in the past), but now are rejected by the underlying f*()
methods with a TypeError
:
$stream->write(false); // would write ''
$stream->write(false); // throws TypeError
TypeError : fwrite() expects parameter 2 to be string, bool given
See my branch for a test / fix specific to write()
; however, there are many other methods that have the same behavior.
We fixed on our end, I just wanted to make sure this behavior was known / documented.
Hello contributors!
I get from my web-server headers like:
X-FORWARDED-HOST = host:port
X-FORWARDED-PORT = port
I am using FilterUsingXForwardedHeaders
filter and in result I get wierd URI from ServerRequestFactory::fromGlobals
with port
duplicated like https://host:port:port/path
.
I guessX-FORWARDED-HOST
in FilterUsingXForwardedHeaders
should be parsed in the same way like ServerRequestFactory::marshalHostAndPortFromHeader()
did.
In earlier versions (updated 2.9.2 -> 2.13.0
) it was working fine. So it's a kind of little BC Break
too :)
I understand that I can implement FilterServerRequestInterface
myself, but it would be more convenient to have such feature out of the box.
Thank you!
Q | A |
---|---|
Version(s) | all |
Environment variables prefixed with CONTENT_
may overwrite HTTP-Headers provided by the client
This may be entirely expected behavior but I have not found a spec or warning in the documentation.
It's a least surprising to find unrelated environment variables as request header and potentially a security problem when comparing the env-variable against the header during authentication.
CONTENT_
will be populated as http-headers:I stumbled about this behavior in a project with the following .env
-file:
CONTENT_API_PASSWORD=password-for-content-api
The .env
file is loaded into the $_SERVER superglobal via Dotenv\Dotenv::createImmutable()
.
The request object is created via \Laminas\Diactoros\ServerRequestFactory::fromGlobals
.
The request object now contains content-api-password
as request-header $request->getHeader('content-api-password')
.
CONTENT_
max overwrite client provided HTTP-Headers:If the same header name is provided by an http-client the $_SERVER
superglobal contains a HTTP_CONTENT_API_PASSWORD
key.
In the current implementation the order is relevant. Whatever key is defined later in $_SERVER
will be used to populate the HTTP-Header.
$server = [
'HTTP_CONTENT_API_PASSWORD' => 'password_from_header',
'CONTENT_API_PASSWORD' => 'password_from_env',
];
$request = ServerRequestFactory::fromGlobals($server);
$this->assertSame('password_from_env', $request->getHeader('content-api-password')[0]);
$server = [
'CONTENT_API_PASSWORD' => 'password_from_env',
'HTTP_CONTENT_API_PASSWORD' => 'password_from_header',
];
$request = ServerRequestFactory::fromGlobals($server);
$this->assertSame('password_from_header', $request->getHeader('content-api-password')[0]);
$server = [
'HTTP_CONTENT_API_PASSWORD' => 'password_from_header',
'CONTENT_API_PASSWORD' => 'password_from_env',
];
$request = ServerRequestFactory::fromGlobals($server);
$this->assertSame('password_from_header', $request->getHeader('content-api-password')[0]);
$server = [
'CONTENT_API_PASSWORD' => 'password_from_env',
'HTTP_CONTENT_API_PASSWORD' => 'password_from_header',
];
$request = ServerRequestFactory::fromGlobals($server);
$this->assertSame('password_from_header', $request->getHeader('content-api-password')[0]);
The following commit causes breaking issues and probably should not have been implemented in a minor version.
zendframework/zend-diactoros@ee4bcdc#diff-5abca4d9d5693da46d10a54795b1192d
Specifically this change breaks laravel/passport ^3.0 which depends on ~1.0 of this library.
I've opened a ticket for them to patch branch 3.x, but that patch may introduce new problems. That's why I'd suggest this update be reverted from 1.x of the zend framework.
Originally posted by @soundsgoodsofar at zendframework/zend-diactoros#356
function getSomeData(): Generator {
yield 1 => 'One';
yield 2 => 'Two';
yield 3 => 'Three';
}
$data = getSomeData();
$json = new Zend\Diactoros\Response\JsonResponse($data);
Response should consume the generator and treat it as if a normal PHP array was passed in:
$data = [
1 => 'One',
2 => 'Two',
3 => 'Three'
];
$json = new Zend\Diactoros\Response\JsonResponse($data);
An error is thrown instead: "Trying to clone an uncloneable object of class Generator..."
Originally posted by @nbish11 at zendframework/zend-diactoros#365
Q | A |
---|---|
New Feature | no |
RFC | no |
BC Break | no |
Currently TextResponse
always allocating new buffer for string output - https://github.com/laminas/laminas-diactoros/blob/2.6.x/src/Response/TextResponse.php#L75
This approach working really slow, my suggestion is to implement pure RAM string stream.
Here is what I mean - https://github.com/makise-co/framework/blob/master/src/Http/FakeStream.php (this approach is ~30% more performant)
Huge memory usage, despite using SapiStreamEmitter
When using Zend\Diactoros\Server with ->setEmitter(new \Zend\Diactoros\Response\SapiStreamEmitter());
, it is still huge memory usage.
here is reproduce code.
<?php
use Zend\Diactoros\Server;
// zendframework/zend-diactoros version is 1.3.10
require_once __DIR__.'/vendor/autoload.php';
$server = Server::createServer(function () {
// create empty 10MB file
// $ dd if=/dev/zero of=tempfile bs=1M count=10
return new \Zend\Diactoros\Response(fopen('tempfile', 'r'));
}, $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
$server->setEmitter(new \Zend\Diactoros\Response\SapiStreamEmitter());
$server->listen();
// for builtin server
file_put_contents("php://stdout", "\nMemory Usage: " . formatBytes(memory_get_peak_usage(true)));
function formatBytes($bytes, $precision = 2) {
if ( abs($bytes) < 1024 ) $precision = 0;
$sign = '';
if ( $bytes < 0 ) {
$sign = '-';
$bytes = abs($bytes);
}
$exp = floor(log($bytes) / log(1024));
$bytes = sprintf('%.'.$precision.'f', ($bytes / pow(1024, floor($exp))));
return $sign . $bytes .' '. ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$exp];
}
expected Memory Usage is 2.00 MB
,
but showned Memory Usage is 16.00 MB
.
I think that reason is Server::listen() calls ob_start
,
but did not clean output buffer (ob_end_clean)
Originally posted by @sasezaki at zendframework/zend-diactoros#232
Perhaps it makes sense to pass to the constructor $attributes like $_ENV.
Originally posted by @easy-system at zendframework/zend-diactoros#156
Hi!
This module is excellent, but in a strict
Hack project, is has no use, unfortunately, because of the lack of typechecker definitions.
Me and, for sure, many others, would appreciate if you wrote typechecker definitions!
Originally posted by @barbu110 at zendframework/zend-diactoros#180
Hi,
Do you know how we can play with SSE ?
I'm trying to use CallbackStream to handle my SSE manager with code like this one:
$stream = new CallbackStream( function () use ( $that ) {
$that->setStart( time() );
echo 'retry: ' . ( $that->client_reconnect * 1000 ) . "\n"; // Set the retry interval for the client
while ( true ) {
// Leave the loop if there are no more handlers
if ( !$that->hasEventListener() ) {
break;
}
if ( $that->isTick() ) {
// No updates needed, send a comment to keep the connection alive.
// From https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events
echo ': ' . sha1( mt_rand() ) . "\n\n";
}
// Start to check for updates
foreach ( $that->getEventListeners() as $event => $handler ) {
if ( $handler->check() ) { // Check if the data is avaliable
$data = $handler->update(); // Get the data
$id = $that->getNewId();
$that->sendBlock( $id, $data, $event );
// Make sure the data has been sent to the client
$that->flush();
}
}
// Break if the time exceed the limit
if ( $that->exec_limit !== 0 && $that->getUptime() > $that->exec_limit ) {
break;
}
// Sleep
$that->sleep();
}
} );
$response = new Response();
$response->withHeader( 'Content-Type', 'text/event-stream' )
->withHeader( 'Cache-Control', 'no-cache' )
->withHeader( 'X-Accel-Buffering', 'no' );
if ( $this->allow_cors ) {
$response->withHeader( 'Access-Control-Allow-Origin', '*' );
$response->withHeader( 'Access-Control-Allow-Credentials', 'true' );
}
if( $this->use_chunked_encoding ) {
$response->withHeader( 'Transfer-encoding', 'chunked' );
}
return $response->withStatus( 200 )->withBody( $stream );
How can I use echo and flush behavior with CallbackStream ?
Regards,
Originally posted by @Wikiki at zendframework/zend-diactoros#229
Why is the new feature needed? What purpose does it serve?
After doing a bit of searching, I didn't find a class that would send a CSV response. There were the Text, JSON, HTML, Redirect, XML, and Empty response classes, but nothing specific to CSV. So I created this PR to add a CSV response class, which can send both plain CSV text as well as a response that will be interpreted by the client as a downloadable file.
How will users use the new feature?
Users can use the CSV response class very similarly to how they use the existing response classes. The only difference is that if they supply a file name as the third parameter to the constructor, then a download response will be sent, not a textual response.
Originally posted by @settermjd at zendframework/zend-diactoros#361
SapiEmitter::emitBody()
is currently:
echo $response->getBody();
The definition of StreamInterface::__toString()
specifies:
This method MUST NOT raise an exception in order to conform with PHP's string casting operations.
and Zend\Diactoros\Stream::__toString()
indeed suppresses run-time exceptions:
try {
$this->rewind();
return $this->getContents();
} catch (RuntimeException $e) {
return '';
}
GuzzleHttp\Psr7\Stream::__toString()
even suppresses all exceptions:
try {
$this->seek(0);
return (string) stream_get_contents($this->stream);
} catch (\Exception $e) {
return '';
}
As a practical result of this, whenever there is an exception in the process of reading the stream, instead of it bubbling up to the next matching catch
block or to the exception handler to notify the developer, the HTTP client gets an empty response instead. In other words, an error which the developer could have been notified of directly via their error reporting system is instead translated into something the user must report manually. I don't want to wait for our users to submit bugs when they see a white page. I want errors going to our error reporting system wherever possible (currently Bugsnag) so we know about it immediately.
I don't know what "conform with PHP's string casting operations means", but I did find this:
Johannes explained that there is no way to ensure that an exception thrown during a cast to string would be handled correctly by the Zend Engine, and that this won't change unless large parts of the Engine are rewritten.
So it seems to me, __toString()
in PHP is basically broken when it comes to exceptions. Either you allow it to throw exceptions, in which case they don't bubble and you get a fatal error instead, or you suppress the exceptions and turn them into a broken result which the user has to report. Neither are ideal.
I therefore propose that SapiEmitter::emitBody()
not depend on __toString()
and instead be:
$stream = $response->getBody();
if ($stream->isSeekable()) {
$stream->rewind();
}
echo $stream->getContents();
Do you think this would be a good idea?
Originally posted by @jesseschalken at zendframework/zend-diactoros#181
Two tests are looking identical despite description. Probably withHost in second case should be replaced by something else.
public function testUriDoesNotAppendColonToHostIfPortIsEmpty()
{
$uri = (new Uri())->withHost('google.com');
$this->assertSame('//google.com', (string) $uri);
}
public function testAuthorityIsPrefixedByDoubleSlashIfPresent()
{
$uri = (new Uri())->withHost('example.com');
$this->assertSame('//example.com', (string) $uri);
}
Originally posted by @Keksov at zendframework/zend-diactoros#381
zend-expressive v2.0.2
Do not know if this is a known issue or if this has already been fixed. The issue exists in zend-expressive v2.0.2. When accessing url query parameters via $request->getQueryParams()
, integer values will be cast to string (e.g. 2
=> "2"
). I discovered this on parsing a query string encoded with http_build_query
on an array.
Same thing happens when applying parse_str($_SERVER['QUERY_STRING'])
.
$request->getQueryParams()
casts integers to strings
$aggregates = [
'aggregates' => [
'number' => 1
]
];
$client->request('GET', $myServiceUrl . '?' . http_build_query($aggregates))
[myServiceUrl]?aggregates%5Bnumber%5D=1
to your service$params = $request->getQueryParams();
var_dump($params['aggregates']) // number: "1"
1
stays 1
😎
FYI @heiglandreas
In ServerRequestFactory::fromGlobals()
, most of the code checks if the arguments are truthy, rather than set. For example:
This becomes an issue during testing (or a long-running process that accepts multiple requests). For example:
// In a previous test
$_POST = ['foo' => 'bar'];
// In current test
$request = ServerRequestFactory::fromGlobals(null, null, []);
echo serialize($request->getParsedBody());
// Expected: a:0:{}
// Actual: a:1:{s:3:"foo";s:3:"bar";}
Improved versions would be:
$server = static::normalizeServer($server ?? $_SERVER);
$files = static::normalizeFiles($files ?? $_FILES);
$cookies ?? $_COOKIE,
$query ?? $_GET,
$body ?? $_POST,
Or for under PHP 7:
$server = static::normalizeServer(isset($server) ? $server : $_SERVER);
$files = static::normalizeFiles(isset($files) ? $files : $_FILES);
isset($cookies) ? $cookies : $_COOKIE,
isset($query) ? $query : $_GET,
isset($body) ? $body : $_POST,
(This code should fix the issues, but I didn't have time to write any tests right now, so I'm opening an issue instead of a pr.)
Originally posted by @0b10011 at zendframework/zend-diactoros#281
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These problems occurred while renovating this repository. View logs.
These updates are awaiting their schedule. Click on a checkbox to get an update now.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
composer.json
php ~8.1.0 || ~8.2.0 || ~8.3.0
psr/http-factory ^1.0.2
psr/http-message ^1.1 || ^2.0
http-interop/http-factory-tests ^0.9.0
laminas/laminas-coding-standard ~2.5.0
php-http/psr7-integration-tests ^1.3
phpunit/phpunit ^9.6.16
psalm/plugin-phpunit ^0.19.0
vimeo/psalm ^5.22.1
.github/workflows/continuous-integration.yml
.github/workflows/docs-build.yml
.github/workflows/release-on-milestone-closed.yml
::getRequestTarget()
http://tools.ietf.org/html/rfc7230#section-5.3.1
The most common form of request-target is the origin-form.
origin-form = absolute-path [ "?" query ]
The query component is optional, the path is required.
But in code if exists only query - return it
$target = $this->uri->getPath();
if ($this->uri->getQuery()) {
$target .= '?' . $this->uri->getQuery();
}
if (empty($target)) {
$target = '/';
}
probably it would be correct:
$target = $this->uri->getPath();
if (! $target) {
$target = '/';
}
if ($this->uri->getQuery()) {
$target .= '?' . $this->uri->getQuery();
}
return $target;
Originally posted by @easy-system at zendframework/zend-diactoros#153
Q | A |
---|---|
New Feature | yes |
RFC | no |
BC Break | no |
Hey there 👋
I'm working on a project that could use the Laminas\Diactoros\Request\ArraySerializer
and Laminas\Diactoros\Response\ArraySerializer
. However I don't want to force the users of my package to use the laminas-diactoros
PSR-7 implementation.
Any chance the serializers could be altered to use the PSR-17 factories and extracted to a separate package? I.e. something like this:
<?php
declare(strict_types=1);
namespace Laminas\Diactoros\Serializer;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Throwable;
use function sprintf;
final class ArraySerializer
{
private RequestFactoryInterface $requestFactory;
private StreamFactoryInterface $streamFactory;
public function __construct(
\Psr\Http\Message\RequestFactoryInterface $requestFactory,
\Psr\Http\Message\StreamFactoryInterface $streamFactory
) {
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
}
public static function toArray(RequestInterface $request) : array
{
// ...
}
public function fromArray(array $serializedRequest) : RequestInterface
{
$uri = self::getValueFromKey($serializedRequest, 'uri');
$method = self::getValueFromKey($serializedRequest, 'method');
$request = $this->requestFactory->createRequest($method, $uri);
$body = $this->streamFactory->createStream(self::getValueFromKey($serializedRequest,
'body'));
// ...
}
}
Thanks!
HTTP/1.1 provides 'Transfer-encoding: chunked' for sending a stream of data from the server.
Zend Diactoros currently doesn't seem to provide out-of-the-box support for this.
Response
that sets the required headers for you.StreamInterface
that supports dynamic content and is compatible with the requirements of SapiStreamEmitter
.Possible solution for out-of-the-box support could be:
DynamicStreamInterface
interface that extends StreamInterface
, for type-hinting in a momentGeneratorStream
class that implements DynamicStreamInterface
, and its read()
method gets the required data from a PHP generatorDynamicStreamInterface
, and its read()
method gets the required data from a PHP callbackStreamResponse
class that requires a DynamicStreamInterface
in the constructor, sets the necessary headers for chunked stream deliveryIt leaves questions around handling $maxBufferLength
, and where the code goes to write the actual chunked blocks (trait? helper class?).
I have to build a solution for this, and I'm happy to contribute it back to Diactoros, if we can agree a design :)
Originally posted by @stuartherbert at zendframework/zend-diactoros#261
the current Builds are failing, because the testcase loads the latest version from here: https://www.iana.org/assignments/http-status-codes/http-status-codes.xml
This was Last Updated on 2021-10-01
Fix in #78
Here, at getpocket.com, we have had a client hit our servers with header keys as integers. In doing so HeaderSecurity::assertValidName
is throwing an exception because ! is_string(-1) === true
.
I have been unable to identify any documentation which would suggest that these values could be valid. At this point I believe the best behavior is to ignore these keys.
Originally posted by @jeshuaborges at zendframework/zend-diactoros#286
Q | A |
---|---|
QA | yes |
As decided during the Technical-Steering-Committee Meeting on August 3rd, 2020, Laminas wants to implement vimeo/psalm in all packages.
Implementing psalm is quite easy.
.psalm.xml.dist
in the project root$ composer require vimeo/psalm
$ vendor/bin/psalm --set-baseline=psalm-baseline.xml
static-analysis
with the command psalm --shepherd --stats
script:
in .travis.yml
: - if [[ $TEST_COVERAGE == 'true' ]]; then composer static-analysis ; fi
phpstan.neon.dist
, .travis.yml
entry, composer.json
require-dev
and scripts
)Why is_uploaded_file
missing in ->moveTo
? but specified in https://github.com/zendframework/zend-diactoros/blob/master/src/UploadedFile.php#L135
Originally posted by @undercloud at zendframework/zend-diactoros#190
Q | A |
---|---|
Version(s) | 2.4.0 |
Using a GD resource works fine until there is interaction with the stream.
When working with GD Images, most of the methods of the Stream (with PHP 8.0 its GDImage
) will result in errors.
public function testIsReableWithGdResource()
{
$resource = imagecreate(1, 1);
$stream = new Stream($resource);
$this->assertTrue($stream->isReadable());
}
public function testCloseWithGdResource()
{
$resource = imagecreate(1, 1);
$stream = new Stream($resource);
$this->assertNull($stream->close());
}
result in
1) LaminasTest\Diactoros\StreamTest::testIsReableWithGdResource
stream_get_meta_data(): supplied resource is not a valid stream resource
2) LaminasTest\Diactoros\StreamTest::testCloseWithGdResource
fclose(): supplied resource is not a valid stream resource
So the existing code is already broken and doesn't work with image resources.
Using methods return proper informations such as the GD Image/Resource is not writable, seekable, e.g.
Originally posted by @ADmad in #46 (comment)
My php service generates a very big list of items.
I decided to stream items on the fly as they are generated to save memory.
I implemented response body as a custom stream that wraps a generator which yields items.
Now I need to append a checksum trailer header to that stream response, so clients can check response validity.
What is a proper way of appending trailer header to a stream response in diactoros?
Originally posted by @drscre at zendframework/zend-diactoros#231
I originally opened this ticket with the passport team and they asked me to re-open it over here. The problem happens on passport upgrade, but if you look at the stack trace, you should see that the error message is from symfony/psr-http-message-bridge complaining that a Diactoros method isn't returning the correct value.
After upgrading from passport 7.5.1 -> 8.3.1, with no other changes, a client token request crashes with this error:
device-service-fpm_1 | [2020-02-03 13:56:30] local.ERROR: Return value of Zend\Diactoros\normalizeServer() must be of the type array, none returned {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Return value of Zend\\Diactoros\
device-service-fpm_1 | ormalizeServer() must be of the type array, none returned at /var/www/device-service/vendor/laminas/laminas-diactoros/src/functions/normalize_server.legacy.php:22)
device-service-fpm_1 | [stacktrace]
device-service-fpm_1 | #0 /var/www/device-service/vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php(52): Zend\\Diactoros\
device-service-fpm_1 | ormalizeServer()
device-service-fpm_1 | #1 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php(131): Symfony\\Bridge\\PsrHttpMessage\\Factory\\DiactorosFactory->createRequest()
device-service-fpm_1 | #2 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\\Routing\\RoutingServiceProvider->Illuminate\\Routing\\{closure}()
device-service-fpm_1 | #3 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Container/Container.php(681): Illuminate\\Container\\Container->build()
device-service-fpm_1 | #4 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Container/Container.php(629): Illuminate\\Container\\Container->resolve()
device-service-fpm_1 | #5 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(769): Illuminate\\Container\\Container->make()
device-service-fpm_1 | #6 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php(79): Illuminate\\Foundation\\Application->make()
device-service-fpm_1 | #7 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php(46): Illuminate\\Routing\\ControllerDispatcher->transformDependency()
device-service-fpm_1 | #8 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php(27): Illuminate\\Routing\\ControllerDispatcher->resolveMethodDependencies()
device-service-fpm_1 | #9 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(41): Illuminate\\Routing\\ControllerDispatcher->resolveClassMethodDependencies()
device-service-fpm_1 | #10 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()
device-service-fpm_1 | #11 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()
device-service-fpm_1 | #12 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Router.php(681): Illuminate\\Routing\\Route->run()
device-service-fpm_1 | #13 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
device-service-fpm_1 | #14 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #15 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Illuminate\\Routing\\Middleware\\ThrottleRequests->handle()
device-service-fpm_1 | #16 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #17 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Router.php(683): Illuminate\\Pipeline\\Pipeline->then()
device-service-fpm_1 | #18 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Router.php(658): Illuminate\\Routing\\Router->runRouteWithinStack()
device-service-fpm_1 | #19 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Router.php(624): Illuminate\\Routing\\Router->runRoute()
device-service-fpm_1 | #20 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Routing/Router.php(613): Illuminate\\Routing\\Router->dispatchToRoute()
device-service-fpm_1 | #21 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(170): Illuminate\\Routing\\Router->dispatch()
device-service-fpm_1 | #22 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
device-service-fpm_1 | #23 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #24 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
device-service-fpm_1 | #25 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #26 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
device-service-fpm_1 | #27 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #28 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
device-service-fpm_1 | #29 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(62): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #30 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode->handle()
device-service-fpm_1 | #31 /var/www/device-service/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #32 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Fideloper\\Proxy\\TrustProxies->handle()
device-service-fpm_1 | #33 /var/www/device-service/vendor/barryvdh/laravel-cors/src/HandlePreflight.php(29): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #34 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Barryvdh\\Cors\\HandlePreflight->handle()
device-service-fpm_1 | #35 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
device-service-fpm_1 | #36 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Pipeline\\Pipeline->then()
device-service-fpm_1 | #37 /var/www/device-service/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(110): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
device-service-fpm_1 | #38 /var/www/device-service/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()
device-service-fpm_1 | #39 {main}
device-service-fpm_1 | "}
Is there a particular reason that UploadedFile does not extend SplFileInfo. The only conflicting interface, which isn't even really a conflict so much is getSize()
-- would there be any interest in this? I can do a PR if the interest is there.
I need this feature like yesterday and without it I'm either gonna need to look for an alternative or fork.
Originally posted by @mattsah at zendframework/zend-diactoros#377
Today, I was bitten by the denormalization of URI query-strings between the URI model and the ServerRequest model.
I was delegating a request object, and although the URI included a set of query params, these did not propagate from the URI model to the ServerRequest model - and the consumer component happened to be getting this information from the ServerRequest model rather than from the URI model, and so of course this turned into somewhat of a nightmare of debugging and tracing.
Per the relevant doc-block PSR-7, this denormalization may be expected:
Note: the query params might not be in sync with the URI or server
params. If you need to ensure you are only getting the original
values, you may need to parse the query string fromgetUri()->getQuery()
or from theQUERY_STRING
server param.
Note the precise phrasing: it might not be in sync with the URI model.
IMO, it's extremely problematic that PSR-7 was designed with a built-in denormalization issue, and since the specification seems to imply that implementations might (may) keep these values in sync, I strongly suggest we do that.
Now, I understand that these two models are, strictly speaking, incompatible - because the query string is just a string, and doesn't have to follow the common a=1&b=2
query format, and as such, you can't simply make this data synchronize in both directions.
However, this is still denormalization, and it still causes problems - having to update both values with two representations of the same data basically guarantees inconsistency, which basically guarantees (eventually) bugs.
Since the query string is the most general representation of the query data, I'd suggest treating the query-params in the ServerRequest as an accessor for the query-string inside the URI model.
You can of course still denormalize the query data internally (for performance) but it should behave as though the query-string is one value.
The elephant in the room of course is this: what happens if I ask the ServerRequest model for the query params, and the query-string in the underlying URI model isn't something that can be decoded as query params?
I suspect this question is what lead to apparent overthinking in the design of PSR-7, and I think that one possible answer to that question is actually much simpler than you may be thinking. The answer lies in PHP itself. What does PHP do if you provide a query-string it can't decode? Simple answer: nothing.
While this may seem "wrong" on the surface, it's actually completely appropriate - the $_GET
superglobal, just like the ServerRequest::getQueryParams()
method I'm proposing, provides an API that lets you access correctly encoded query parameters extracted from the query string, only if such paramters are present.
If you think of it that way, if there is an underlying query string in the URI model that doesn't contain such encoded parameters, that's precisely what you'd expect to be able to access: an empty array. If you wanted (or wanted to manipulate) the actual query-string, you'd ask the URI model for that.
I don't believe there's any real disconnect there at all; and it's consistent with PHP's own behavior, and therefore shouldn't really be surprising at all.
As things stand, the model doesn't actually make any sense - what we're dealing with is two representations of the same value, not two different values. The way it's modeled right now, the model actually permits the same property to have two different values at the same time.
I'm sure that makes a bunch of sense to people who understand quantum mechanics, but that's probably not most of us ;-)
The bottom line is that these are two different representations of the query string portion of the request URI, not two different values.
Would you be open to changing this?
Originally posted by @mindplay-dk at zendframework/zend-diactoros#184
The parse_url
function is not multibyte safe, and was never designed to be, which seems to be why this bug from 2010 has still not been addressed. As mentioned in the comments on that bug, a nicer behaviour would be for parse_url
to "treat all extended characters ... as opaque characters and copy them as-is without modification", but I don't see that happening any time soon.
There are a couple of options I can think of to try and support UTF8 URIs in this library;
parse_url
which URL encodes/decodes extended characters (e.g. in this comment from the manual page)parse_url
Or alternatively this library could simply reject URI's with extended characters which aren't properly encoded, as they are technically invalid according to RFC 3986.
Whichever way, it might be worth adding something to the Travis config to run the tests under different locale environments to show how this is reproduced in the unit tests (though this might be a pain, since the available values for the locale are platform dependent).
Originally posted by @Pudge601 in zendframework/zend-diactoros#155 (comment)
Originally posted by @Pudge601 at zendframework/zend-diactoros#354
How about adding an Zend\Diactoros\Response\ImageResponse
that supports the most common file types like JPG, GIF and PNG? How could this be archieved?
Originally posted by @RalfEggert at zendframework/zend-diactoros#141
\Zend\Diactoros\parseCookieHeader
does not properly parse array cookies in header:
testCookie[foo]=fooValue; testCookie[bar]=barValue
this should be ok by php doc here in Example # 3 but the parser does not match it
also in closed issue #272
Originally posted by @mposchl at zendframework/zend-diactoros#360
The library is currently failing a UriIntegrationTest
https://github.com/php-http/psr7-integration-tests
Q | A |
---|---|
Version(s) | 2.8 |
Fails the tests
Run PSR-7 Integration Tests against this library
Pass the tests
Q | A |
---|---|
Version(s) | 2.x |
The Diactoros package currently forces installation of the Zend Framework bridge package, laminas/laminas-zendframework-bridge
. For projects that can switch over easily, this is a completely unnecessary dependency.
Unless I'm missing something crucial here, I would instead recommend placing this dependency in the suggest
configuration of the composer.json
for this library.
Installing laminas/laminas-diactoros
via Composer forces the installation of laminas/laminas-zendframework-bridge
.
composer require laminas/laminas-diactoros
Both the Diactoros library and the Zend Framework bridge are installed by Composer.
Only the Diactoros library is installed by Composer.
Q | A |
---|---|
Version(s) | 2.11.2 |
UploadedFile::moveTo()
doesn't remove the original file when used in CLI context and keep grab the handle. So PHPUnit can't remove setup files. It fixed in this PR.
#98
The uploaded file could not be removed when we called UploadedFile::moveTo().
Instantiate UploadedFile, and post the object in PHPUnit testing, then remove it at tearDown, it could not be remove and raise error 'the file is in busy'.
If UploadedFile::moveTo was called, the original file must be moved.
Discovered while digging in php-http/curl-client#14
Apparently, diactoros defaults the HTTP method when building a new Request('http://example.com')
to ''
(empty string). As far as I know, an empty string is not a valid HTTP method (not sure if that assumption is reflected in the HTTP spec), and therefore the initial state of a diactoros HTTP request is invalid, and should lead to an exception.
Originally posted by @Ocramius at zendframework/zend-diactoros#150
reference: https://www.php.net/manual/en/wrappers.php.php
php://input is not support fstat
Supports stat() | php://memory and php://temp only. |
---|
Provide a narrative description of what you are trying to accomplish.
Originally posted by @zhushengwen at zendframework/zend-diactoros#380
Hi guys, I'm working on an iOS project and I'm required to send documents, such as pdfs, to my laravel server. The issue is, every time I try to send a post request for a PDF, I get this error message: Invalid size provided for UploadedFile; must be an int
. I'm at my wit's end trying to solve this issue. Any ideas?
Originally posted by @ImmanuelKannan at zendframework/zend-diactoros#316
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.