Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
kelunik committed Sep 12, 2017
1 parent 63db43b commit f8275ce
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ defaults:
asset_path: "/socket/.shared/asset"

navigation:
- client
- server
64 changes: 64 additions & 0 deletions docs/client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Client
permalink: /client
---
## Connecting

You can establish a socket connection to a specified URI by using `Amp\Socket\connect`. It will automatically take care of resolving DNS names and will try other IPs if a connection fails and multiple IPs are available via DNS.

```php
/**
* Asynchronously establish a socket connection to the specified URI.
*
* @param string $uri URI in scheme://host:port format. TCP is assumed if no scheme is present.
* @param ClientConnectContext $socketContext Socket connect context to use when connecting.
* @param CancellationToken|null $token
*
* @return Promise<\Amp\Socket\ClientSocket>
*/
function connect(
string $uri,
ClientConnectContext $socketContext = null,
CancellationToken $token = null
): Promise {
/* ... */
}
```

### TLS

If you want to connect via TLS, you can use `Amp\Socket\cryptoConnect()`, which connects to the specified URI and enables TLS in one step.

```php
/**
* Asynchronously establish an encrypted TCP connection (non-blocking).
*
* Note: Once resolved the socket stream will already be set to non-blocking mode.
*
* @param string $uri
* @param ClientConnectContext $socketContext
* @param ClientTlsContext $tlsContext
* @param CancellationToken $token
*
* @return Promise<ClientSocket>
*/
function cryptoConnect(
string $uri,
ClientConnectContext $socketContext = null,
ClientTlsContext $tlsContext = null,
CancellationToken $token = null
): Promise {
/* ... */
}
```

{:.note}
> If you want to connect and enable TLS at a later time, you can use `Socket::enableCrypto()` on the `Socket` instance returned from `connect()`.
## Sending Data

`ClientSocket` implements `OutputStream`, so everything from [`amphp/byte-stream`](https://amphp.org/byte-stream/#outputstream) applies.

## Receiving Data

`ClientSocket` implements `InputStream`, so everything from [`amphp/byte-stream`](https://amphp.org/byte-stream/#inputstream) applies.
51 changes: 49 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
---
title: Socket
title: Socket Overview
permalink: /
---
This documentation does not exist, yet. Please help providing it by submitting a pull-request on GitHub.
`amphp/socket` provides a socket abstraction for clients and servers. It abstracts the really low levels of non-blocking streams in PHP.

## Client Example

```php
$uri = new Uri($argv[1]);
$host = $uri->getHost();

if ($uri->getScheme() === "https") {
/** @var Socket $socket */
$socket = yield cryptoConnect("tcp://" . $host . ":" . $uri->getPort());
} else {
/** @var Socket $socket */
$socket = yield connect("tcp://" . $host . ":" . $uri->getPort());
}

yield $socket->write("GET {$uri} HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n");

while (null !== $chunk = yield $socket->read()) {
print $chunk;
}
```

## Server Example

```php
Loop::run(function () {
$clientHandler = function (ServerSocket $socket) {
list($ip, $port) = explode(":", $socket->getRemoteAddress());

echo "Accepted connection from {$ip}:{$port}." . PHP_EOL;

$body = "Hey, your IP is {$ip} and your local port used is {$port}.";
$bodyLength = \strlen($body);

yield $socket->end("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: {$bodyLength}\r\n\r\n{$body}");
};

$server = Amp\Socket\listen("127.0.0.1:0");

echo "Listening for new connections on " . $server->getAddress() . " ..." . PHP_EOL;
echo "Open your browser and visit http://" . $server->getAddress() . "/" . PHP_EOL;

while ($socket = yield $server->accept()) {
Amp\asyncCall($clientHandler, $socket);
}
});
```
98 changes: 98 additions & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Server
permalink: /server
---

## Listening

To listen on a port or unix domain socket, you can use `Amp\Socket\listen`. It's a wrapper around `stream_socket_server` that gives useful error message on failures via exceptions.

```php
/**
* Listen for client connections on the specified server address.
*
* If you want to accept TLS connections, you have to use `yield $socket->enableCrypto()` after accepting new clients.
*
* @param string $uri URI in scheme://host:port format. TCP is assumed if no scheme is present.
* @param ServerListenContext $socketContext Context options for listening.
* @param ServerTlsContext $tlsContext Context options for TLS connections.
*
* @return Server
*
* @throws SocketException If binding to the specified URI failed.
*/
function listen(string $uri, ServerListenContext $socketContext = null, ServerTlsContext $tlsContext = null): Server {
/* ... */
}
```

## Controlling the `Server`

### Accepting Connections

Once you're listening, you can accept clients using `Server::accept()`. It returns a `Promise` that returns once a new client has been accepted. It's usually called within a `while` loop:

```php
$server = Amp\Socket\listen("tcp://127.0.0.1:1337");

while ($client = yield $server->accept()) {
// do something with $client, which is a ServerSocket instance

// you shouldn't yield here, because that will wait for the yielded promise
// before accepting another client, see below.
}
```

### Handling Connections

It's best to handle clients in their own coroutine, while letting the server accept all clients as soon as there are new clients.

```php
Loop::run(function () {
$clientHandler = function (ServerSocket $socket) {
list($ip, $port) = explode(":", $socket->getRemoteAddress());

echo "Accepted connection from {$ip}:{$port}." . PHP_EOL;

$body = "Hey, your IP is {$ip} and your local port used is {$port}.";
$bodyLength = \strlen($body);

yield $socket->end("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: {$bodyLength}\r\n\r\n{$body}");
};

$server = Amp\Socket\listen("127.0.0.1:0");

echo "Listening for new connections on " . $server->getAddress() . " ..." . PHP_EOL;
echo "Open your browser and visit http://" . $server->getAddress() . "/" . PHP_EOL;

while ($socket = yield $server->accept()) {
Amp\asyncCall($clientHandler, $socket);
}
});
```

### Closing Connections

Once you're done with a client, you can close the connection using `Socket::close()`. If you want to wait for all data to be successfully written before closing the connection, you can use `Socket::end()` with or without a final data chunk. For an example, look at the section above.

## Server Address

Sometimes you don't know the address the server is listening on, e.g. because you listed to `tcp://127.0.0.1:0`, which assigns a random free port. You can use `Server::getAddress()` to get the address the server is bound to.

## Sending Data

`ServerSocket` implements `OutputStream`, so everything from [`amphp/byte-stream`](https://amphp.org/byte-stream/#outputstream) applies.

## Receiving Data

`ServerSocket` implements `InputStream`, so everything from [`amphp/byte-stream`](https://amphp.org/byte-stream/#inputstream) applies.

## Server Shutdown

Once you're done with the server socket, you should close the socket. That means, the server won't listen on the specified location anymore. Use `Server::close()` to close the server socket.

## TLS

As already mentioned in the documentation for `Amp\Socket\listen()`, you need to enable TLS manually after accepting connections. For a TLS server socket, you listen on the `tcp://` protocol on a specified address. After accepting clients you call `$socket->enableCrypto()` where `$socket` is the socket returned from `Server::accept()`.

Any data transmitted before `Socket::enableCrypto()` resolves successfully will be transmitted in clear text. Don't attempt to read from the socket or write to it manually. Doing so will read the raw TLS handshake data that's supposed to be read by OpenSSL.
2 changes: 2 additions & 0 deletions lib/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* @param ServerTlsContext $tlsContext Context options for TLS connections.
*
* @return Server
*
* @throws SocketException If binding to the specified URI failed.
*/
function listen(string $uri, ServerListenContext $socketContext = null, ServerTlsContext $tlsContext = null): Server {
$socketContext = $socketContext ?? new ServerListenContext;
Expand Down

0 comments on commit f8275ce

Please sign in to comment.