forked from docker-php/docker-php
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathRequestWriter.php
More file actions
136 lines (123 loc) · 4.44 KB
/
Copy pathRequestWriter.php
File metadata and controls
136 lines (123 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
namespace Docker\SocketClient;
use Docker\SocketClient\Exception\BrokenPipeException;
use Psr\Http\Message\RequestInterface;
/**
* Method for writing request.
*
* Mainly used by SocketHttpClient
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
trait RequestWriter
{
/**
* Write a request to a socket.
*
* @param resource $socket
* @param RequestInterface $request
* @param int $bufferSize
*
* @throws BrokenPipeException
*/
protected function writeRequest($socket, RequestInterface $request, int $bufferSize = 8192): void
{
if (false === $this->fwrite($socket, $this->transformRequestHeadersToString($request))) {
throw new BrokenPipeException('Failed to send request, underlying socket not accessible, (BROKEN EPIPE)', $request);
}
if ($request->getBody()->isReadable()) {
$this->writeBody($socket, $request, $bufferSize);
}
}
/**
* Write Body of the request.
*
* @param resource $socket
* @param RequestInterface $request
* @param int $bufferSize
*
* @throws BrokenPipeException
*/
protected function writeBody($socket, RequestInterface $request, int $bufferSize = 8192): void
{
$body = $request->getBody();
if ($body->isSeekable()) {
$body->rewind();
}
while (!$body->eof()) {
$buffer = $body->read($bufferSize);
if (false === $this->fwrite($socket, $buffer)) {
throw new BrokenPipeException('An error occur when writing request to client (BROKEN EPIPE)', $request);
}
}
}
/**
* Produce the header of request as a string based on a PSR Request.
*
* @param RequestInterface $request
*
* @return string
*/
protected function transformRequestHeadersToString(RequestInterface $request): string
{
$message = vsprintf('%s %s HTTP/%s', [
strtoupper($request->getMethod()),
$request->getRequestTarget(),
$request->getProtocolVersion(),
])."\r\n";
foreach ($request->getHeaders() as $name => $values) {
$message .= $name.': '.implode(', ', $values)."\r\n";
}
$message .= "\r\n";
return $message;
}
/**
* Replace fwrite behavior as api is broken in PHP.
*
* @see https://secure.phabricator.com/rPHU69490c53c9c2ef2002bc2dd4cecfe9a4b080b497
*
* @param resource $stream The stream resource
* @param string $bytes Bytes written in the stream
*
* @return bool|int false if pipe is broken, number of bytes written otherwise
*/
private function fwrite($stream, string $bytes): bool|int
{
if (!strlen($bytes)) {
return 0;
}
$result = @fwrite($stream, $bytes);
if (0 !== $result) {
// In cases where some bytes are witten (`$result > 0`) or
// an error occurs (`$result === false`), the behavior of fwrite() is
// correct. We can return the value as-is.
return $result;
}
// If we make it here, we performed a 0-length write. Try to distinguish
// between EAGAIN and EPIPE. To do this, we're going to `stream_select()`
// the stream, write to it again if PHP claims that it's writable, and
// consider the pipe broken if the write fails.
$read = [];
$write = [$stream];
$except = [];
@stream_select($read, $write, $except, 0);
if (!$write) {
// The stream isn't writable, so we conclude that it probably really is
// blocked and the underlying error was EAGAIN. Return 0 to indicate that
// no data could be written yet.
return 0;
}
// If we make it here, PHP **just** claimed that this stream is writable, so
// perform a write. If the write also fails, conclude that these failures are
// EPIPE or some other permanent failure.
$result = @fwrite($stream, $bytes);
if (0 !== $result) {
// The write worked or failed explicitly. This value is fine to return.
return $result;
}
// We performed a 0-length write, were told that the stream was writable, and
// then immediately performed another 0-length write. Conclude that the pipe
// is broken and return `false`.
return false;
}
}