Completed
Pull Request — master (#68)
by
unknown
01:33
created

PSR7Client::normalizeHttpProtocolVersion()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
/**
3
 * High-performance PHP process supervisor and load balancer written in Go
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\RoadRunner;
9
10
use Http\Factory\Diactoros;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestFactoryInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Message\StreamFactoryInterface;
15
use Psr\Http\Message\UploadedFileFactoryInterface;
16
17
/**
18
 * Manages PSR-7 request and response.
19
 */
20
class PSR7Client
21
{
22
    /** @var Worker */
23
    private $worker;
24
25
    /** @var ServerRequestFactoryInterface */
26
    private $requestFactory;
27
28
    /** @var StreamFactoryInterface */
29
    private $streamFactory;
30
31
    /*** @var UploadedFileFactoryInterface */
32
    private $uploadsFactory;
33
34
    /** @var array Valid values for HTTP protocol version */
35
    public static $allowedProtocolVersions = [
36
        '1.0',
37
        '1.1',
38
        '2',
39
    ];
40
41
    /**
42
     * @param Worker                             $worker
43
     * @param ServerRequestFactoryInterface|null $requestFactory
44
     * @param StreamFactoryInterface|null        $streamFactory
45
     * @param UploadedFileFactoryInterface|null  $uploadsFactory
46
     */
47
    public function __construct(
48
        Worker $worker,
49
        ServerRequestFactoryInterface $requestFactory = null,
50
        StreamFactoryInterface $streamFactory = null,
51
        UploadedFileFactoryInterface $uploadsFactory = null
52
    ) {
53
        $this->worker = $worker;
54
        $this->requestFactory = $requestFactory ?? new Diactoros\ServerRequestFactory();
55
        $this->streamFactory = $streamFactory ?? new Diactoros\StreamFactory();
56
        $this->uploadsFactory = $uploadsFactory ?? new Diactoros\UploadedFileFactory();
57
    }
58
59
    /**
60
     * @return Worker
61
     */
62
    public function getWorker(): Worker
63
    {
64
        return $this->worker;
65
    }
66
67
    /**
68
     * @return ServerRequestInterface|null
69
     */
70
    public function acceptRequest()
71
    {
72
        $body = $this->worker->receive($ctx);
73
        if (empty($body) && empty($ctx)) {
74
            // termination request
75
            return null;
76
        }
77
78
        if (empty($ctx = json_decode($ctx, true))) {
79
            // invalid context
80
            return null;
81
        }
82
83
        $_SERVER = $this->configureServer($ctx);
84
85
        $request = $this->requestFactory->createServerRequest(
86
            $ctx['method'],
87
            $ctx['uri'],
88
            $_SERVER
89
        );
90
91
        parse_str($ctx['rawQuery'], $query);
92
93
        $request = $request
94
            ->withProtocolVersion(static::normalizeHttpProtocolVersion($ctx['protocol']))
95
            ->withCookieParams($ctx['cookies'])
96
            ->withQueryParams($query)
0 ignored issues
show
Bug introduced by
It seems like $query can also be of type null; however, Psr\Http\Message\ServerR...face::withQueryParams() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
97
            ->withUploadedFiles($this->wrapUploads($ctx['uploads']));
98
99
        foreach ($ctx['attributes'] as $name => $value) {
100
            $request = $request->withAttribute($name, $value);
101
        }
102
103
        foreach ($ctx['headers'] as $name => $value) {
104
            $request = $request->withHeader($name, $value);
105
        }
106
107
        if ($ctx['parsed']) {
108
            $request = $request->withParsedBody(json_decode($body, true));
109
        } else {
110
            if ($body !== null) {
111
                $request = $request->withBody($this->streamFactory->createStream($body));
0 ignored issues
show
Bug introduced by
It seems like $body defined by $this->worker->receive($ctx) on line 72 can also be of type object<Error>; however, Psr\Http\Message\StreamF...terface::createStream() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
112
            }
113
        }
114
115
        return $request;
116
    }
117
118
    /**
119
     * Send response to the application server.
120
     *
121
     * @param ResponseInterface $response
122
     */
123
    public function respond(ResponseInterface $response)
124
    {
125
        $headers = $response->getHeaders();
126
        if (empty($headers)) {
127
            // this is required to represent empty header set as map and not as array
128
            $headers = new \stdClass();
129
        }
130
131
        $this->worker->send($response->getBody(), json_encode([
132
            'status'  => $response->getStatusCode(),
133
            'headers' => $headers
134
        ]));
135
    }
136
137
    /**
138
     * Returns altered copy of _SERVER variable. Sets ip-address,
139
     * request-time and other values.
140
     *
141
     * @param array $ctx
142
     * @return array
143
     */
144
    protected function configureServer(array $ctx): array
145
    {
146
        $server = $_SERVER;
147
        $server['REQUEST_TIME'] = time();
148
        $server['REQUEST_TIME_FLOAT'] = microtime(true);
149
        $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1';
150
151
        return $server;
152
    }
153
154
    /**
155
     * Wraps all uploaded files with UploadedFile.
156
     *
157
     * @param array $files
158
     *
159
     * @return array
160
     */
161
    private function wrapUploads($files): array
162
    {
163
        if (empty($files)) {
164
            return [];
165
        }
166
167
        $result = [];
168
        foreach ($files as $index => $f) {
169
            if (!isset($f['name'])) {
170
                $result[$index] = $this->wrapUploads($f);
171
                continue;
172
            }
173
174
            if (UPLOAD_ERR_OK === $f['error']) {
175
                $stream = $this->streamFactory->createStreamFromFile($f['tmpName']);
176
            } else {
177
                $stream = $this->streamFactory->createStream();
178
            }
179
180
            $result[$index] = $this->uploadsFactory->createUploadedFile(
181
                $stream,
182
                $f['size'],
183
                $f['error'],
184
                $f['name'],
185
                $f['mime']
186
            );
187
        }
188
189
        return $result;
190
    }
191
192
    /**
193
     * Normalize HTTP protocol version to valid values
194
     * @param string $version
195
     * @return string
196
     */
197
    public static function normalizeHttpProtocolVersion(string $version): string
198
    {
199
        $v = substr($version, 5);
200
201
        if ($v === '2.0') {
202
            $v = '2';
203
        }
204
205
        // Fallback for values outside of valid protocol versions
206
        if (!in_array($v, static::$allowedProtocolVersions, true)) {
207
            return '1.1';
208
        }
209
210
        return $v;
211
    }
212
}
213