Repository navigation
Expand file tree
/
Copy pathHttpWriter.php
More file actions
104 lines (91 loc) · 3.21 KB
/
Copy pathHttpWriter.php
File metadata and controls
104 lines (91 loc) · 3.21 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
<?php
declare(strict_types=1);
namespace WappDebugger;
/**
* Pushes the events to the receiver of the viewer (spec/ingest.yaml) instead of a file:
* in batches, the rest when the request ends, and what it has every second in a long run.
* It needs no extension: PHP streams send the batch (https needs openssl, as for any https).
* One failed send stops logging, so a missing viewer never slows the application down twice.
*/
final class HttpWriter implements Writer
{
private const TIMEOUT_SECONDS = 2.0;
private const BATCH_BYTES = 512 * 1024;
private string $url;
private string $token;
/** @var callable(string, string, string): bool */
private $send;
/** @var callable(): float */
private $now;
private int $batchLines;
private string $buffer = '';
private int $lines = 0;
private float $since = 0.0;
private bool $failed = false;
/**
* @param (callable(string, string, string): bool)|null $send url, token and body; true when taken
* @param (callable(): float)|null $now seconds, for tests
*/
public function __construct(string $url, string $token, ?callable $send = null, int $batchLines = 100, ?callable $now = null)
{
$this->url = $url;
$this->token = $token;
$this->send = $send ?? [self::class, 'post'];
$this->batchLines = max(1, $batchLines);
$this->now = $now ?? static function (): float {
return microtime(true);
};
}
public function write(string $line): bool
{
if ($this->failed) {
return false;
}
if ($this->lines === 0) {
$this->since = ($this->now)();
}
$this->buffer .= $line . "\n";
++$this->lines;
if ($this->lines >= $this->batchLines || strlen($this->buffer) >= self::BATCH_BYTES || ($this->now)() - $this->since >= 1.0) {
return $this->flush();
}
return true;
}
public function close(): void
{
if (!$this->failed && $this->lines > 0) {
$this->flush();
}
}
private function flush(): bool
{
$body = $this->buffer;
$this->buffer = '';
$this->lines = 0;
if (!($this->send)($this->url, $this->token, $body)) {
$this->failed = true;
return false;
}
return true;
}
/** POSTs a batch with PHP streams; true for a 2xx answer. */
public static function post(string $url, string $token, string $body): bool
{
$context = stream_context_create(['http' => [
'method' => 'POST',
'header' => ['Content-Type: application/x-ndjson', 'Authorization: Bearer ' . $token],
'content' => $body,
'timeout' => self::TIMEOUT_SECONDS,
'ignore_errors' => true,
'follow_location' => 0,
]]);
$stream = @fopen($url, 'rb', false, $context);
if ($stream === false) {
return false;
}
$meta = stream_get_meta_data($stream);
fclose($stream);
$status = is_array($meta['wrapper_data'] ?? null) ? (string) ($meta['wrapper_data'][0] ?? '') : '';
return preg_match('~^HTTP/\S+ 2\d\d~', $status) === 1;
}
}