Completed
Push — feature/EVO-4702 ( fbb50e )
by Leonard
14:02
created

ProxyController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 17
ccs 0
cts 17
cp 0
rs 9.4285
cc 1
eloc 15
nc 1
nop 7
crap 2
1
<?php
2
/**
3
 * ProxyController
4
 */
5
6
namespace Graviton\ProxyBundle\Controller;
7
8
use Graviton\ExceptionBundle\Exception\NotFoundException;
9
use Graviton\ProxyBundle\Service\ApiDefinitionLoader;
10
use Graviton\ProxyBundle\Service\TransformationHandler;
11
use GuzzleHttp\Exception\ClientException;
12
use GuzzleHttp\Exception\ServerException;
13
use Proxy\Proxy;
14
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
15
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
16
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
17
use Symfony\Component\HttpFoundation\HeaderBag;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20
21
/**
22
 * general controller for all proxy staff
23
 *
24
 * @package Graviton\ProxyBundle\Controller
25
 * @author  List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
26
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
27
 * @link    http://swisscom.ch
28
 */
29
class ProxyController
30
{
31
    /**
32
     * @var Proxy
33
     */
34
    private $proxy;
35
36
    /**
37
     * @var EngineInterface
38
     */
39
    private $templating;
40
41
    /**
42
     * @var DiactorosFactory
43
     */
44
    private $diactorosFactory;
45
46
    /**
47
     * @var ApiDefinitionLoader
48
     */
49
    private $apiLoader;
50
51
    /**
52
     * @var HttpFoundationFactory
53
     */
54
    private $httpFoundationFactory;
55
56
    /**
57
     * @var array
58
     */
59
    private $proxySourceConfiguration;
60
61
    /**
62
     * @var TransformationHandler
63
     */
64
    private $transformationHandler;
65
66
    /**
67
     * Constructor
68
     *
69
     * @param Proxy                 $proxy                    proxy
70
     * @param EngineInterface       $templating               twig templating engine
71
     * @param ApiDefinitionLoader   $loader                   definition loader
72
     * @param DiactorosFactory      $diactorosFactory         convert HttpFoundation objects to PSR-7
73
     * @param HttpFoundationFactory $httpFoundationFactory    convert PSR-7 interfaces to HttpFoundation
74
     * @param TransformationHandler $transformationHandler    transformation handler
75
     * @param array                 $proxySourceConfiguration Set of sources to be recognized by the controller.
76
     */
77
    public function __construct(
78
        Proxy $proxy,
79
        EngineInterface $templating,
80
        ApiDefinitionLoader $loader,
81
        DiactorosFactory $diactorosFactory,
82
        HttpFoundationFactory $httpFoundationFactory,
83
        TransformationHandler $transformationHandler,
84
        array $proxySourceConfiguration
85
    ) {
86
        $this->proxy = $proxy;
87
        $this->templating = $templating;
88
        $this->apiLoader = $loader;
89
        $this->diactorosFactory = $diactorosFactory;
90
        $this->httpFoundationFactory = $httpFoundationFactory;
91
        $this->proxySourceConfiguration = $proxySourceConfiguration;
92
        $this->transformationHandler = $transformationHandler;
93
    }
94
95
    /**
96
     * action for routing all requests directly to the third party API
97
     *
98
     * @param Request $request request
99
     *
100
     * @return \Psr\Http\Message\ResponseInterface|Response
101
     */
102
    public function proxyAction(Request $request)
103
    {
104
        $api = $this->decideApiAndEndpoint($request->getUri());
105
        $this->registerProxySources($api['apiName']);
106
107
        $url = $this->apiLoader->getEndpoint($api['endpoint'], true);
108
        if (parse_url($url, PHP_URL_SCHEME) === false) {
109
            $scheme = $request->getScheme();
110
            $url = $scheme.'://'.$url;
111
        }
112
        $response = null;
113
        try {
114
            $newRequest = Request::create(
115
                $url,
116
                $request->getMethod(),
117
                array (),
118
                array (),
119
                array (),
120
                array (),
121
                $request->getContent(false)
0 ignored issues
show
Bug introduced by
It seems like $request->getContent(false) targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, Symfony\Component\HttpFoundation\Request::create() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
122
            );
123
            $newRequest->headers->add($request->headers->all());
124
            $newRequest->query->add($request->query->all());
125
            $newRequest->server->add($request->server->all());
126
127
            $newRequest = $this->transformationHandler->transformRequest(
128
                $api['apiName'],
129
                $api['endpoint'],
130
                $request,
131
                $newRequest
132
            );
133
            $psrRequest = $this->diactorosFactory->createRequest($newRequest);
134
            $psrRequest = $psrRequest->withUri($psrRequest->getUri()->withPort(parse_url($url, PHP_URL_PORT)));
135
            $psrResponse = $this->proxy->forward($psrRequest)->to($this->getHostWithScheme($url));
136
            $response = $this->httpFoundationFactory->createResponse($psrResponse);
137
            $this->cleanResponseHeaders($response->headers);
138
            $this->transformationHandler->transformResponse(
139
                $api['apiName'],
140
                $api['endpoint'],
141
                $response,
142
                clone $response
143
            );
144
        } catch (ClientException $e) {
145
            $response = $e->getResponse();
146
        } catch (ServerException $serverException) {
147
            $response = $serverException->getResponse();
148
        }
149
150
        return $response;
151
    }
152
153
    /**
154
     * Removes some headers from the thirdparty API's response. These headers get always invalid by graviton's
155
     * forwarding and should therefore not be delivered to the client.
156
     *
157
     * @param HeaderBag $headers The headerbag holding the thirdparty API's response headers
158
     *
159
     * @return void
160
     */
161
    protected function cleanResponseHeaders(HeaderBag $headers)
162
    {
163
        $headers->remove('transfer-encoding'); // Chunked responses get not automatically re-chunked by graviton
164
        $headers->remove('trailer'); // Only for chunked responses, graviton should re-set this when chunking
165
    }
166
167
    /**
168
     * get schema info
169
     *
170
     * @param Request $request request
171
     *
172
     * @return Response
173
     */
174
    public function schemaAction(Request $request)
175
    {
176
        $api = $this->decideApiAndEndpoint($request->getUri());
177
        $this->registerProxySources($api['apiName']);
178
        $schema = $this->apiLoader->getEndpointSchema(urldecode($api['endpoint']));
179
        $schema = $this->transformationHandler->transformSchema(
180
            $api['apiName'],
181
            $api['endpoint'],
182
            $schema,
183
            clone $schema
184
        );
185
        $response = new Response(json_encode($schema), 200);
186
        $response->headers->set('Content-Type', 'application/json');
187
188
        return $this->templating->renderResponse(
189
            'GravitonCoreBundle:Main:index.json.twig',
190
            array ('response' => $response->getContent()),
191
            $response
192
        );
193
    }
194
195
    /**
196
     * get API name and endpoint from the url (third party API)
197
     *
198
     * @param string $url the url
199
     *
200
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
201
     */
202
    protected function decideApiAndEndpoint($url)
203
    {
204
        $path = parse_url($url, PHP_URL_PATH);
205
206
        $pattern = array (
207
            "@schema\/@",
208
            "@\/3rdparty\/@",
209
            "@\/item$@",
210
        );
211
        $path = preg_replace($pattern, '', $path);
212
213
        //get api name and endpoint
214
        $apiName = substr($path, 0, strpos($path, '/'));
215
        $endpoint = str_replace($apiName, '', $path);
216
217
        return array (
218
            "apiName" => $apiName,
219
            "endpoint" => $endpoint,
220
        );
221
    }
222
223
    /**
224
     * Registers configured external services to be proxied.
225
     *
226
     * @param string $apiPrefix The prefix of the API
227
     *
228
     * @return void
229
     */
230
    private function registerProxySources($apiPrefix = '')
231
    {
232
        if (array_key_exists('swagger', $this->proxySourceConfiguration)) {
233
            foreach ($this->proxySourceConfiguration['swagger'] as $config) {
234
                if ($apiPrefix == $config['prefix']) {
235
                    $this->apiLoader->setOption($config);
236
                    return;
237
                }
238
            }
239
        }
240
        $e = new NotFoundException('No such thirdparty API.');
241
        $e->setResponse(Response::create());
242
        throw $e;
243
    }
244
245
    /**
246
     * get host, scheme and port
247
     *
248
     * @param string $url the url
249
     *
250
     * @return string
251
     */
252
    private function getHostWithScheme($url)
253
    {
254
        $components = parse_url($url);
255
        $host = $components['scheme'].'://'.$components['host'];
256
        if (!empty($components['port'])) {
257
            $host .= ':'.$components['port'];
258
        }
259
260
        return $host;
261
    }
262
}
263