Completed
Push — master ( 99d635...873c8d )
by Narcotic
35:41 queued 30:38
created

ProxyController   B

Complexity

Total Complexity 19

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 18
dl 0
loc 265
ccs 0
cts 149
cp 0
rs 7.3333
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
C proxyAction() 0 72 8
A cleanResponseHeaders() 0 11 2
A schemaAction() 0 22 1
A decideApiAndEndpoint() 0 20 1
A registerProxySources() 0 15 4
A getHostWithScheme() 0 10 2
1
<?php
2
/**
3
 * ProxyController
4
 */
5
6
namespace Graviton\ProxyBundle\Controller;
7
8
use Graviton\ExceptionBundle\Exception\NotFoundException;
9
use Graviton\ProxyBundle\Exception\TransformationException;
10
use Graviton\ProxyBundle\Service\ApiDefinitionLoader;
11
use Graviton\ProxyBundle\Service\TransformationHandler;
12
use GuzzleHttp\Exception\ClientException;
13
use GuzzleHttp\Exception\RequestException;
14
use GuzzleHttp\Exception\ServerException;
15
use Proxy\Proxy;
16
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
17
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
18
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
19
use Symfony\Component\HttpFoundation\HeaderBag;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
23
24
/**
25
 * general controller for all proxy staff
26
 *
27
 * @package Graviton\ProxyBundle\Controller
28
 * @author  List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
29
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
30
 * @link    http://swisscom.ch
31
 */
32
class ProxyController
33
{
34
    /**
35
     * @var Proxy
36
     */
37
    private $proxy;
38
39
    /**
40
     * @var EngineInterface
41
     */
42
    private $templating;
43
44
    /**
45
     * @var DiactorosFactory
46
     */
47
    private $diactorosFactory;
48
49
    /**
50
     * @var ApiDefinitionLoader
51
     */
52
    private $apiLoader;
53
54
    /**
55
     * @var HttpFoundationFactory
56
     */
57
    private $httpFoundationFactory;
58
59
    /**
60
     * @var array
61
     */
62
    private $proxySourceConfiguration;
63
64
    /**
65
     * @var TransformationHandler
66
     */
67
    private $transformationHandler;
68
69
    /**
70
     * Constructor
71
     *
72
     * @param Proxy                 $proxy                    proxy
73
     * @param EngineInterface       $templating               twig templating engine
74
     * @param ApiDefinitionLoader   $loader                   definition loader
75
     * @param DiactorosFactory      $diactorosFactory         convert HttpFoundation objects to PSR-7
76
     * @param HttpFoundationFactory $httpFoundationFactory    convert PSR-7 interfaces to HttpFoundation
77
     * @param TransformationHandler $transformationHandler    transformation handler
78
     * @param array                 $proxySourceConfiguration Set of sources to be recognized by the controller.
79
     */
80
    public function __construct(
81
        Proxy $proxy,
82
        EngineInterface $templating,
83
        ApiDefinitionLoader $loader,
84
        DiactorosFactory $diactorosFactory,
85
        HttpFoundationFactory $httpFoundationFactory,
86
        TransformationHandler $transformationHandler,
87
        array $proxySourceConfiguration
88
    ) {
89
        $this->proxy = $proxy;
90
        $this->templating = $templating;
91
        $this->apiLoader = $loader;
92
        $this->diactorosFactory = $diactorosFactory;
93
        $this->httpFoundationFactory = $httpFoundationFactory;
94
        $this->proxySourceConfiguration = $proxySourceConfiguration;
95
        $this->transformationHandler = $transformationHandler;
96
    }
97
98
    /**
99
     * action for routing all requests directly to the third party API
100
     *
101
     * @param Request $request request
102
     *
103
     * @return \Psr\Http\Message\ResponseInterface|Response
104
     */
105
    public function proxyAction(Request $request)
106
    {
107
        $api = $this->decideApiAndEndpoint($request->getUri());
108
        $this->registerProxySources($api['apiName']);
109
        $this->apiLoader->addOptions($api);
110
111
        $url = $this->apiLoader->getEndpoint($api['endpoint'], true);
112
        if (parse_url($url, PHP_URL_SCHEME) === null) {
113
            $scheme = $request->getScheme();
114
            $url = $scheme.'://'.$url;
115
        }
116
        $response = null;
117
        try {
118
            $newRequest = Request::create(
119
                $url,
120
                $request->getMethod(),
121
                array (),
122
                array (),
123
                array (),
124
                array (),
125
                $request->getContent(false)
126
            );
127
            $newRequest->headers->add($request->headers->all());
128
            $newRequest->query->add($request->query->all());
129
            $queryString = $request->server->get('QUERY_STRING');
130
            $newRequest->server->set('QUERY_STRING', $queryString);
131
132
            $newRequest = $this->transformationHandler->transformRequest(
133
                $api['apiName'],
134
                $api['endpoint'],
135
                $request,
136
                $newRequest
137
            );
138
139
            $psrRequest = $this->diactorosFactory->createRequest($newRequest);
140
141
            $psrRequestUri = $psrRequest->getUri();
142
            $psrRequestUriPort = parse_url($url, PHP_URL_PORT);
143
            if (is_numeric($psrRequestUriPort) || is_null($psrRequestUriPort)) {
144
                $psrRequestUri = $psrRequestUri->withPort($psrRequestUriPort);
145
            }
146
147
            $psrRequest = $psrRequest->withUri($psrRequestUri);
148
            $psrResponse = $this->proxy->forward($psrRequest)->to($this->getHostWithScheme($url));
149
            $response = $this->httpFoundationFactory->createResponse($psrResponse);
150
            $this->cleanResponseHeaders($response->headers);
151
            $this->transformationHandler->transformResponse(
152
                $api['apiName'],
153
                $api['endpoint'],
154
                $response,
155
                clone $response
156
            );
157
        } catch (ClientException $e) {
158
            $response = $e->getResponse();
159
        } catch (ServerException $serverException) {
160
            $response = $serverException->getResponse();
161
        } catch (TransformationException $e) {
162
            $message = json_encode(
163
                ['code' => 404, 'message' => 'HTTP 404 Not found']
164
            );
165
166
            throw new NotFoundHttpException($message, $e);
167
        } catch (RequestException $e) {
168
            $message = json_encode(
169
                ['code' => 404, 'message' => 'HTTP 404 Not found']
170
            );
171
172
            throw new NotFoundHttpException($message, $e);
173
        }
174
175
        return $response;
176
    }
177
178
    /**
179
     * Removes some headers from the thirdparty API's response. These headers get always invalid by graviton's
180
     * forwarding and should therefore not be delivered to the client.
181
     *
182
     * @param HeaderBag $headers The headerbag holding the thirdparty API's response headers
183
     *
184
     * @return void
185
     */
186
    protected function cleanResponseHeaders(HeaderBag $headers)
187
    {
188
        $headers->remove('transfer-encoding'); // Chunked responses get not automatically re-chunked by graviton
189
        $headers->remove('trailer'); // Only for chunked responses, graviton should re-set this when chunking
190
191
        // Change naming to Graviton output Allow options
192
        if ($allow = $headers->get('Allow', false)) {
193
            $headers->set('Access-Control-Allow-Methods', $allow);
194
            $headers->remove('Allow');
195
        }
196
    }
197
198
    /**
199
     * get schema info
200
     *
201
     * @param Request $request request
202
     *
203
     * @return Response
204
     */
205
    public function schemaAction(Request $request)
206
    {
207
        $api = $this->decideApiAndEndpoint($request->getUri());
208
        $this->registerProxySources($api['apiName']);
209
        $this->apiLoader->addOptions($api);
210
211
        $schema = $this->apiLoader->getEndpointSchema(urldecode($api['endpoint']));
212
        $schema = $this->transformationHandler->transformSchema(
213
            $api['apiName'],
214
            $api['endpoint'],
215
            $schema,
216
            clone $schema
217
        );
218
        $response = new Response(json_encode($schema), 200);
219
        $response->headers->set('Content-Type', 'application/json');
220
221
        return $this->templating->renderResponse(
222
            'GravitonCoreBundle:Main:index.json.twig',
223
            array ('response' => $response->getContent()),
224
            $response
225
        );
226
    }
227
228
    /**
229
     * get API name and endpoint from the url (third party API)
230
     *
231
     * @param string $url the url
232
     *
233
     * @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...
234
     */
235
    protected function decideApiAndEndpoint($url)
236
    {
237
        $path = parse_url($url, PHP_URL_PATH);
238
239
        $pattern = array (
240
            "@schema\/@",
241
            "@\/3rdparty\/@",
242
            "@\/item$@",
243
        );
244
        $path = preg_replace($pattern, '', $path);
245
246
        //get api name and endpoint
247
        $apiName = substr($path, 0, strpos($path, '/'));
248
        $endpoint = str_replace($apiName, '', $path);
249
250
        return array (
251
            "apiName" => $apiName,
252
            "endpoint" => $endpoint,
253
        );
254
    }
255
256
    /**
257
     * Registers configured external services to be proxied.
258
     *
259
     * @param string $apiPrefix The prefix of the API
260
     *
261
     * @return void
262
     */
263
    private function registerProxySources($apiPrefix = '')
264
    {
265
        foreach (array_keys($this->proxySourceConfiguration) as $source) {
266
            foreach ($this->proxySourceConfiguration[$source] as $config) {
267
                if ($apiPrefix == $config['prefix']) {
268
                    $this->apiLoader->setOption($config);
269
                    return;
270
                }
271
            }
272
        }
273
274
        $e = new NotFoundException('No such thirdparty API.');
275
        $e->setResponse(Response::create());
276
        throw $e;
277
    }
278
279
    /**
280
     * get host, scheme and port
281
     *
282
     * @param string $url the url
283
     *
284
     * @return string
285
     */
286
    private function getHostWithScheme($url)
287
    {
288
        $components = parse_url($url);
289
        $host = $components['scheme'].'://'.$components['host'];
290
        if (!empty($components['port'])) {
291
            $host .= ':'.$components['port'];
292
        }
293
294
        return $host;
295
    }
296
}
297