Test Failed
Branch master (9dd5cd)
by Blackred
03:57
created

PassThroughController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 1
eloc 9
nc 1
nop 4
1
<?php declare(strict_types = 1);
2
3
namespace Wolnosciowiec\WebProxy\Controllers;
4
5
use GuzzleHttp\Exception\ConnectException;
6
use GuzzleHttp\Exception\RequestException;
7
use GuzzleHttp\Exception\ServerException;
8
use function GuzzleHttp\json_encode;
9
use GuzzleHttp\Psr7\Response;
10
use Psr\Log\LoggerInterface;
11
use Wolnosciowiec\WebProxy\Factory\ProxyClientFactory;
12
use Wolnosciowiec\WebProxy\Factory\RequestFactory;
13
14
/**
15
 * @package Wolnosciowiec\WebProxy\Controllers
16
 */
17
class PassThroughController
18
{
19
    /**
20
     * @var int $retries
21
     */
22
    private $retries = 0;
23
24
    /**
25
     * @var int $maxRetries
26
     */
27
    private $maxRetries = 3;
28
29
    /**
30
     * @var ProxyClientFactory $clientFactory
31
     */
32
    private $clientFactory;
33
34
    /**
35
     * @var RequestFactory $requestFactory
36
     */
37
    private $requestFactory;
38
39
    /**
40
     * @var LoggerInterface $logger
41
     */
42
    private $logger;
43
44
    public function __construct(
45
        int $maxRetries = 3,
46
        ProxyClientFactory $clientFactory,
47
        RequestFactory $requestFactory,
48
        LoggerInterface $logger
49
    ) {
50
        $this->requestFactory = $requestFactory;
51
        $this->maxRetries    = $maxRetries;
52
        $this->clientFactory = $clientFactory;
53
        $this->logger        = $logger;
54
    }
55
56
    /**
57
     * @throws \Exception
58
     * @return string
59
     */
60
    private function getRequestedURL()
61
    {
62
        if (!isset($_SERVER['HTTP_WW_TARGET_URL'])) {
63
            throw new \Exception('Request URL not specified. Should be in a header "WW_TARGET_URL"');
64
        }
65
66
        return $_SERVER['HTTP_WW_TARGET_URL'];
67
    }
68
69
    /**
70
     * @throws \Exception
71
     * @return \GuzzleHttp\Psr7\ServerRequest
72
     */
73
    private function getRequest()
74
    {
75
        return $this->requestFactory->create($this->getRequestedURL());
76
    }
77
78
    /**
79
     * @throws \Exception
80
     * @return Response
81
     */
82
    public function executeAction(): Response
83
    {
84
        try {
85
            $request = $this->getRequest();
86
87
        } catch (\Exception $e) {
88
            $this->logger->error('Invalid request: ' . $e->getMessage());
89
90
            return new Response(400, [], json_encode([
91
                'success' => false,
92
                'message' => $e->getMessage(),
93
            ]));
94
        }
95
96
        try {
97
            $this->logger->notice('Forwarding to "' . $this->getRequestedURL() . '"');
98
99
            // forward the request and get the response.
100
            $response = $this->clientFactory->create()
101
                ->forward($request)
102
                ->to($this->getRequestedURL());
103
104
        } catch (RequestException $e) {
105
106
            // try again in case of connection failure
107
            if (($e instanceof ConnectException || $e instanceof ServerException)
108
                && $this->maxRetries > $this->retries
109
            ) {
110
                $this->retries++;
111
112
                $this->logger->error('Retrying request(' . $this->retries . '/' . $this->maxRetries . ')');
113
                return $this->executeAction();
114
            }
115
116
            $response = $e->getResponse();
117
118
            if (!$response instanceof Response) {
119
                $response = new Response(500, [], $e->getMessage());
120
                $this->logger->notice('Error response: ' . $e->getMessage());
121
            }
122
        }
123
124
        return $this->fixResponseHeaders($response);
125
    }
126
127
    private function fixResponseHeaders(Response $response)
128
    {
129
        // fix: empty response if page is using gzip (Zend Diactoros is trying to do the same, but it's doing it incorrectly)
130
        if (!$response->hasHeader('Content-Length')) {
131
            $response = $response->withAddedHeader('Content-Length', strlen((string)$response->getBody()));
132
        }
133
134
        // we are not using any encoding at the output
135
        $response = $response->withoutHeader('Transfer-Encoding');
136
137
        return $response;
138
    }
139
140
    /**
141
     * @codeCoverageIgnore
142
     * @param string $code
143
     * @throws \InvalidArgumentException
144
     */
145
    public function sendResponseCode($code)
146
    {
147
        if (defined('IS_EMULATED_ENVIRONMENT') && IS_EMULATED_ENVIRONMENT) {
148
            return;
149
        }
150
151
        if ($code === 404) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $code (string) and 404 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
152
            header('HTTP/1.0 404 Not Found');
153
            return;
154
        }
155
        elseif ($code === 403) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $code (string) and 403 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
156
            header('HTTP 1.1 403 Unauthorized');
157
            return;
158
        }
159
        elseif ($code === 400) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $code (string) and 400 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
160
            header('HTTP/1.0 400 Bad Request');
161
            return;
162
        }
163
164
        throw new \InvalidArgumentException('Unrecognized code passed');
165
    }
166
}
167