GithubHelp home page GithubHelp logo

reactphp / http-client Goto Github PK

View Code? Open in Web Editor NEW
230.0 230.0 65.0 181 KB

[Deprecated] Event-driven, streaming HTTP client for ReactPHP.

Home Page: https://reactphp.org/http-client/

License: MIT License

PHP 100.00%
http http-client php reactphp

http-client's Introduction

ReactPHP Logo

Event-driven, non-blocking I/O with PHP.

Build Status

ReactPHP is a low-level library for event-driven programming in PHP. At its core is an event loop, on top of which it provides low-level utilities, such as: Streams abstraction, async DNS resolver, network client/server, HTTP client/server and interaction with processes. Third-party libraries can use these components to create async network clients/servers and more.

<?php

// $ composer require react/http react/socket # install example using Composer
// $ php example.php # run example on command line, requires no additional web server

require __DIR__ . '/vendor/autoload.php';

$server = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

$socket = new React\Socket\SocketServer('127.0.0.1:8080');
$server->listen($socket);

echo "Server running at http://127.0.0.1:8080" . PHP_EOL;

This simple web server written in ReactPHP responds with "Hello World!" for every request.

ReactPHP is production ready and battle-tested with millions of installations from all kinds of projects around the world. Its event-driven architecture makes it a perfect fit for efficient network servers and clients handling hundreds or thousands of concurrent connections, long-running applications and many other forms of cooperative multitasking with non-blocking I/O operations. What makes ReactPHP special is its vivid ecosystem with hundreds of third-party libraries allowing you to integrate with many existing systems, such as common network services, database systems and other third-party APIs.

  • Production ready and battle-tested.
  • Rock-solid with stable long-term support (LTS) releases.
  • Requires no extensions and runs on any platform - no excuses!
  • Takes advantage of optional extensions to get better performance when available.
  • Highly recommends latest version of PHP 7+ for best performance and support.
  • Supports legacy PHP 5.3+ and HHVM for maximum compatibility.
  • Well designed and reusable components.
  • Decoupled parts so they can be replaced by alternate implementations.
  • Carefully tested (unit & functional).
  • Promotes standard PSRs where possible for maximum interoperability.
  • Aims to be technology neutral, so you can use your preferred application stack.
  • Small core team of professionals supported by large network of outside contributors.

ReactPHP is non-blocking by default. Use workers for blocking I/O. The event loop is based on the reactor pattern (hence the name) and strongly inspired by libraries such as EventMachine (Ruby), Twisted (Python) and Node.js (V8).

This repository you're currently looking at is mostly used as a meta repository to discuss and plan all things @ReactPHP. See the individual components linked below for more details about each component, its documentation and source code.

Core Components

Network Components

Utility Components

Built with ReactPHP

  • Thruway PHP Client and Router Library for Autobahn and WAMP (Web Application Messaging Protocol) for Real-Time Application Messaging voryx/Thruway

  • PPM - PHP Process Manager PPM is a process manager, supercharger and load balancer for modern PHP applications. php-pm/php-pm

  • php-ar-drone 🚁 Port of node-ar-drone which allows user to control a Parrot AR Drone over PHP jolicode/php-ar-drone

  • Ratchet Asynchronous WebSocket server ratchetphp/Ratchet

  • Predis\Async Asynchronous PHP client library for Redis built on top of ReactPHP nrk/predis-async

  • clue/redis-server A Redis server implementation in pure PHP clue/redis-server

And many more on our wiki page »

Articles

  • Sergey Zhuk A series of articles covering ReactPHP: from the basics to the real application examples. sergeyzhuk.me

  • Cees-Jan Kiewiet Blog series about several ReactPHP components and how they work. blog.wyrihaximus.net

  • Loïc Faugeron Super Speed Symfony - ReactPHP. gnugat.github.io

  • Marc J. Schmidt Bring High Performance Into Your PHP App (with ReactPHP). marcjschmidt.de

  • Marc Morera When ReactPHP meet Symfony medium.com/@apisearch

Talks

Getting started

ReactPHP consists of a set of individual components. This means that instead of installing something like a "ReactPHP framework", you actually pick only the components that you need.

This project follows SemVer for all its stable components. The recommended way to install these components is through Composer. New to Composer?

For example, this may look something like this:

# recommended install: pick required components
composer require react/event-loop react/http

As an alternative, we also provide a meta package that will install all stable components at once. Installing this is only recommended for quick prototyping, as the list of stable components may change over time. This meta package can be installed like this:

# quick protoyping only: install all stable components
composer require react/react:^1.4

For more details, check out ReactPHP's homepage for quickstart examples and usage details.

See also the combined changelog for all ReactPHP components for details about version upgrades.

Support

Do you have a question and need help with ReactPHP? Don't worry, we're here to help!

As a first step, check the elaborate documentation that comes with each component (see links to individual documentation for each component above). If you find your question is not answered within the documentation, there's a fair chance that it may be relevant to more people. Please do not hesitate to file your question as an issue in the relevant component so others can also participate.

You can also check out our official Gitter chat room. Most of the people involved in this project are available in this chat room, so many questions get answered in a few minutes to some hours. We also use this chat room to announce all new releases and ongoing development efforts, so consider staying in this chat room for a little longer.

Also follow @reactphp on Twitter for updates. We use this mostly for noteworthy, bigger updates and to keep the community updated about ongoing development efforts. You can always use the #reactphp hashtag if you have anything to share!

We're a very open project and we prefer public communication whenever possible, so that more people can participate and help getting the best solutions available. At the same time, we realize that some things are better addressed in private. Whether you just want to say thank you, want to report a security issue or want to help sponsor a certain feature development, you can reach out to the core team in private by sending an email to [email protected]. Please keep in mind that we're a small team of volunteers and do our best to support anybody reaching out.

Do you want to support ReactPHP? Awesome! Let's start with letting the the world know why you think ReactPHP is awesome and try to help others getting on board! Send a tweet, write a blog post, give a talk at your local user group or conference or even write a book. There are many ways you can help. You can always reach out to us in private and help others in our support channels. Thank you!

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

The test suite also contains a number of functional integration tests that rely on a stable internet connection. Due to the vast number of integration tests, these are skipped by default during CI runs. If you also do not want to run these, they can simply be skipped like this:

vendor/bin/phpunit --exclude-group internet

License

MIT, see LICENSE.

http-client's People

Contributors

arnaud-lb avatar carusogabriel avatar cboden avatar clue avatar cursedcoder avatar djagya avatar dpovshed avatar e3betht avatar igorw avatar jmalloc avatar jsor avatar mbonneau avatar mdrost avatar mmelvin0 avatar reedy avatar remicollet avatar th3mouk avatar weichenlin avatar wyrihaximus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

http-client's Issues

Support for Expect: 100 continue

Now when we support Transfer-Encoding: chunked it would be great to have at least basic continue support. I'll file example MR soon

Simplify acessing response body

Accessing the HTTP response body is likely one of the main use cases for this library, however it's actually unnecessarily cumbersome.

This has come up several times already, so we way want to look into providing a simpler API.

Possibly related to #41.

POST method doesn't working

I'm using this code to send a POST request but it's not working.

<?php

require 'vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dnsResolver = $dnsResolverFactory->createCached('8.8.8.8', $loop);

$factory = new React\HttpClient\Factory();
$client = $factory->create($loop, $dnsResolver);

$request = $client->request('POST', 'http://respondto.it/bomber');

$request->write("test=12345");

$request->on(
    'response',
    function ($response) {
        echo "recived".PHP_EOL;
        $response->on(
            'data',
            function ($data) {
                var_dump($data);
            }
        );
    }
);
$request->end();
$loop->run();

Result is

POST /bomber
Headers

Connect-Time: 1
Connection: close
Host: respondto.it
Total-Route-Time: 0
User-Agent: React/alpha
Version: HTTP/1.1
Via: 1.1 vegur
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Request-Id: 6596bd3e-1505-45a9-adbd-a0a5f4c7ea52

I know that i'm doing something wrong but i couldn't find solution.

Time Out Question

http-client How to set time out
I am Chinese, My English is not good Please understand

Add docs for request error handling

Examples miss a really important point: error handly for a request like a simple timeout.
simply like this:

$request = $client->request('GET', $url);
$request->on('error', function ($e) {
  $socketerror = $e->getPrevious();
  echo "request failed with error: " . $e->getMessage() . PHP_EOL;
});

I spend too much time just for this so i think it should be added in the README or the exemple section.

Unclear event semantics

The Response is a ReadableStreamInterface, however it does not currently obey its event semantics:

  • It will never emit a close event
  • It will emit an end event if the underlying stream closes
  • It will emit an end event with an error if the underlying stream ends

We should obey the stream semantics here, see also reactphp/stream#59.

Note that this will result in a BC break, so we should target the v0.5.0 release here. Even the examples (#69) highlight how the end event should actually be a close event instead.

In-built request timeout functionality

Currently, request timeouts can be implemented somewhat messily by specifying loop timers and attempting to manually close the request. They should ideally be in-built.

Unable to validate "27C" as chunk length header

I don't know how to reproduce problem using only this library, but with WyriHaximus/react-guzzle-psr7 this code

<?php

require_once __DIR__ . '/vendor/autoload.php';

$loop = React\EventLoop\Factory::create();
$handler = new WyriHaximus\React\GuzzlePsr7\HttpClientAdapter($loop);
$client = new GuzzleHttp\Client([
    'handler' => GuzzleHttp\HandlerStack::create($handler),
]);

$request = new GuzzleHttp\Psr7\Request('GET', 'https://wizzair.com/');
$promise = $client->sendAsync($request)->then(function (Psr\Http\Message\ResponseInterface $response) {
    echo (string)$response->getBody();
});

$promise->wait();

produces

PHP Fatal error:  Uncaught Exception: Unable to validate "27C" as chunk length header in /home/mateusz/git/react-guzzle-test/vendor/react/http-client/src/ChunkedStreamDecoder.php:122
Stack trace:
#0 /home/mateusz/git/react-guzzle-test/vendor/react/http-client/src/ChunkedStreamDecoder.php(70): React\HttpClient\ChunkedStreamDecoder->iterateBuffer()
#1 [internal function]: React\HttpClient\ChunkedStreamDecoder->handleData('="Original" src...', Object(React\Stream\Stream))
#2 /home/mateusz/git/react-guzzle-test/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(64): call_user_func_array(Array, Array)
#3 /home/mateusz/git/react-guzzle-test/vendor/react/stream/src/Stream.php(173): Evenement\EventEmitter->emit('data', Array)
#4 [internal function]: React\Stream\Stream->handleData(Resource id #96, Object(React\EventLoop\StreamSelectLoop))
#5 /home/mateusz/git/react-guzzle-test/vendor/react/event-loop/src/StreamSelectLoop.php(232): call_user_func(Array, Resource id #96, Object(React\EventLoop\StreamSelectLoop))
#6 /home/mat in /home/mateusz/git/react-guzzle-test/vendor/react/http-client/src/Request.php on line 150

Allow client creation without DNS resolver

According to https://github.com/reactphp/socket-client/blob/master/src/Connector.php#L9 Connector is deprecated. This could allow to change https://github.com/reactphp/http-client/blob/master/src/Factory.php#L12 by providing a default NULL resolver and in that case use a TcpConnector instead of a DnsConnector.

This would require upping socket-client dependency to 0.5 from 0.3 but would allow reducing some boilerplate code especially for simple test apps and local installations.

Roadmap to stable v1.0.0

Let's face it, this project is stable and has been used in production for years :shipit:

However, we're currently following a v0.X.Y release scheme (http://sentimentalversioning.org/).

We should finally make this explicit and fully adhere to SemVer and release a stable v1.0.0.

To a large extend, a stable v1.0.0 helps making BC breaks more explicit and thus the whole project more reliable from a consumer perspective. This project is actively maintained and has received some major updates in the last weeks and has some major updates planned in the next weeks. Given our current versioning scheme, we'd like to ensure all anticipated BC breaks will be merged before the planned v1.0.0 release.

As such, I've set up a roadmap that enlists only the major changes for each version among with planned release dates towards a stable v1.0.0 release:

v0.4.11 ✅

  • Released 2016-09-15
  • Decode chunked transfer encoding

v0.4.x ✅

v0.4.17 ✅

  • Released 2017-03-20
  • Uppercase chunk headers

v0.5.0 ✅

  • Released 2017-05-22
  • Stream semantics
  • Replace SocketClient with Socket
  • Cancellation support
  • No longer considered beta quality

v0.6.0

  • Planned 2019-??
  • Request promise

v0.7.0

  • Planned 2019-??
  • PSR-7 response
  • Reduce public API

v0.8.0

  • Planned 2019-??
  • Merge this component into Http

v1.0.0

  • Planned 2019-??
  • No new changes planned, this should merely mark the previous release as "stable"

This ticket aims to serve as a basic overview and does not contain every single change. Please also see the milestone links and the CHANGELOG for more details.

Obviously, this roadmap is subject to change and I'll try to keep it updated as we progress. In order to avoid cluttering this, please keep discussion in this ticket to a minimum and consider reaching out to us through new tickets or Twitter etc.

Error when sending large messages over secure HTTPS with older PHP versions

When sending large contents via POST, specifically images (~90kb+) the connection simply hangs. I have already filed the same bug in for the WyriHaximus guzzle adapter here: WyriHaximus/react-guzzle-psr7#10.

Here is some short example code:

<?php

require_once 'vendor/autoload.php';


$mp = new p3k\Multipart;
$mp->addPart('field', 'value');
$mp->addFile('file', 'large-image', 'image/jpeg');

$url = "https://example.com";

$loop = React\EventLoop\Factory::create();
$dnsFactory = new React\Dns\Resolver\Factory();
$dns = $dnsFactory->createCached('192.168.2.1', $loop);
$factory = new React\HttpClient\Factory();
$client = $factory->create($loop, $dns);

$loop->nextTick(function() use ($client, $url, $mp) {

    $request = $client->request("POST", $url, [
        'Content-Type'      => $mp->contentType(),
        'Content-Length'    => strlen($mp->data())
    ]);

    $request->on('response', function($response) {
        print "WE HAVE RESPONSE: ".$response->getCode()."\n";
        print_r($response->getHeaders());
        $response->on('data', function($data, $response) {
            print "DATA ...\n";
            print $data;
        });
        $response->on('end', function() {
            die("IT'S OVER!\n");
        });
    });

    $request->end($mp->data());
});

while (1) $loop->run();
print "DONE!\n";

You will need a URL to POST to as well as an image file to send. Both should not be hard to get a hold of to test this.

Error on "close" event

i wanted to reject the promise based on timer to cancel the request.
Kindly advise

PHP Fatal error: Uncaught TypeError: Argument 1 passed to {closure}() must be an instance of Exception, none given, called in /vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php on line 123

public function call()
    {
        $this->request = $this->client->request($this->method, $this->url);
        $deferred = new Deferred();
        $this->request->on('response', function (\React\HttpClient\Response $response)use ($deferred) {
            $response->on('data', function ($chunk) use ($deferred){
                    $deferred->resolve($chunk);
            });
        });
        $this->request->on('error', function (\Exception $e) use ($deferred){
            $deferred->reject($e->getMessage());
        });

Error thrown here
        // $this->request->on('close', function (\Exception $e) use ($deferred){
        //    $deferred->reject($e->getMessage());
        // });
        
        $this->request->end();
        return $deferred->promise();
    }

    public function close(){
        $this->request->close();
    }
$mainLoop->addTimer(15.0, function () use ($requestEndpoints) {
        $requestEndpoints->close();
    });

client pool

if i have thousands of requests needed to send,how can i set a http client pool?

Hanging connector

require_once __DIR__.'/vendor/autoload.php';

$inner = count($argv) > 1;

$loop = new \React\EventLoop\ExtEventLoop();

$connector = new React\Socket\Connector($loop);

runme($loop, $connector, $inner);

if (!$inner) {
    echo "Outer start\n";
    $loop->run();
}

function runme(\React\EventLoop\LoopInterface $loop, React\Socket\Connector $connector, $inner)
{
    $connector->connect('tcp://127.0.0.1:3306')->
    then(function (\React\Socket\ConnectionInterface $conn) {
        echo ("Hello MySQL!\n");
        $conn->close();
    },function ($e) {
        echo ("Bye MySQL!\n");
    })->done();

    if ($inner) {
        echo "Inner start\n";
        $loop->run();
    }

    echo "Exit runme\n";
}

it works proper and returns:

$ php ./testMysql.php 
Exit runme
Outer start
Hello MySQL!
$ php ./testMysql.php  --inner
Inner start
Hello MySQL!
Exit runme

But if you go into \React\Socket\TcpConnector::waitForStreamOnce() and remove $canceller function in new Promise object like below, than it hangs again. Looks like it works in latest version of react a kind of accidentally as socket not stored obvious way, and in fact similar to code in v0.4.6.

private function waitForStreamOnce($stream)
    {
        $loop = $this->loop;

        return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
            $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
                $loop->removeWriteStream($stream);

                // The following hack looks like the only way to
                // detect connection refused errors with PHP's stream sockets.
                if (false === stream_socket_get_name($stream, true)) {
                    fclose($stream);

                    $reject(new \RuntimeException('Connection refused'));
                } else {
                    $resolve(new Connection($stream, $loop));
                }
            });
        });
    }

$ php ./testMysql.php  --inner
Inner start
.....HANGING
$ php ./testMysql.php 
Exit runme
Outer start
...HANGING

Initial discussion here: https://stackoverflow.com/questions/47551054/php-7-1-pecl-event-libevent-is-hanging-in-weird-case

[bug]Concurrent with more than 1000 requests.

Hello there

I met two problem.

  • Concurrent with more than 1000 requests.

I find that if I send more than 1000 requests at the same time, some requests will get timeout exception.

  • unable handle http message with bad separator:

Some http header and body are concatenated by "\n\n", not "\r\n\r\n". this package cannnot handle theme correctly, but browser can.
ps: I encountered this problem on win, I do not know Linux will not have this problem

Failed to access a TLS server

Trying to access TLS servers, I keep getting errors. For example when trying to access https://www.google.com , I get this:

An error occurred in the underlying stream:
Unable to complete SSL/TLS handshake:
stream_socket_enable_crypto():
Peer certificate CN=`www.google.com' did not match expected CN=`74.125.140.106'

My code is:

        $request = $this->client->request('GET', 'https://www.google.com', [ ]);
        $request->on('error', function ($err) use ($callback) { $callback($err, NULL); });
        $request->on('response', function ($response) {
            $response->on('data', function ($data, $response) {
                echo "Got response data";
                var_dump([$data, $response]);
            });
        });
        $request->end();

question regarding writing

Hello,
Just a question, using the function $connection->write($data); is it blocking or sync?
I mean if i have to write 10000 times on a connection and one of these times it takes a while for some reasons, will it delay all the other writings or looping the write function and then $loop->run(); , everything starts as unique thread?

Thanks

Delay Request Connection

I am working on a project which will make a lot of connections to external APIs and I need to queue the requests so that I can limit the number of active requests. This is needed to manage the load on the API (also may be useful for APIs that enforce a request limit).

For this reason I want to build the request and attach all event handlers, also write some data, but not yet send it.

Currently write() will directly open the connection and send the data.

http-client/src/Request.php

Lines 103 to 107 in d779a3b

// otherwise buffer and try to establish connection
$this->pendingWrites .= $data;
if (self::STATE_WRITING_HEAD > $this->state) {
$this->writeHead();
}

I suggest to add a method that allows adding data to $pendingWrites without sending the request.

Response can sometimes emits data twice with incompatible arguments

I'm struggling to reproduce this and I can't anymore, but it's fairly clear in the code this is possible. There are two places where the data event for a Response can be triggered.

In Response::handleData the arguments are string, Response.

In Request::handleData the arguments are Psr\Http\Message\StreamInterface, Response.

The latter seems to be correct, happens all the time, and what I want to incrementally read from the stream, so I just wrap my own handleData logic in $stream instanceof StreamInterface but that feels smelly.

The thing I can't reproduce is when Response::handleData gets called. It was happening to me every single time the responding server threw a fatal PHP error, now I can't make it happen. Also of interesting note it was returning only part of the PHP error text, although the stream itself yielded the entire text.

Also of note this is with v0.4.10 (I'll update to the latest) but the code is identical in your master branch.

Incomplete control code while parsing chunked stream

Hi,

I'm using wyrihaximus/react-guzzle-psr7 to send async requests with Guzzle HTTP, however, the server I am requesting has recently started using chunked HTTP requests. I saw that chunked requests was recently introduced into the HTTP client by the creator of the package I am using however I am still getting the error Stream ended with incomplete control code. Is this a client side problem?

Thanks

Call to undefined method React\HttpClient\ChunkedStreamDecoder::end()

Hello,

I encountered the following error using the react http client (v0.4.11) :

Call to undefined method React\HttpClient\ChunkedStreamDecoder::end()

Indeed in Response.php it seem that $this->stream->end()is not (always?) resolved

Fixed my issue by getting back to 0.4.10.

Cheers.

Response->on('end') signature weirdness?

I've got the following simple code which sends a request and displays the response.

Problem: the echoed buffer will always be string(0) unless I declare it as &buffer in the on('end'). string(0) is how the buffer is initialized:

$request = $client->request(
	'POST',
	$url,
	array(
		'Content-Length' => strlen($data),
		'Content-Type' => 'application/json'
	)
);

$request->on('response', function ($response) {
	$buffer = '';

	$response->on('data', function ($data, $response) use (&$buffer) {
		$buffer .= (string) $data;
	});

	$response->on('end', function ($error) use ($response, $buffer /* < by ref matters */) {
		echo(sprintf("HTTP %d\n", $response->getCode()));
		if ($buffer) {
			echo($buffer . PHP_EOL);
		}
	});
});

$request->end($data);

Might this be a problem with PHP-internal optimization?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.