Completed
Push — master ( 11b317...37df4d )
by Lucas
09:27
created

ProxyController::decideApiAndEndpoint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 16
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
nop 1
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
            $queryString = $request->server->get('QUERY_STRING');
126
            $newRequest->server->set('QUERY_STRING', $queryString);
127
128
            $newRequest = $this->transformationHandler->transformRequest(
129
                $api['apiName'],
130
                $api['endpoint'],
131
                $request,
132
                $newRequest
133
            );
134
            $psrRequest = $this->diactorosFactory->createRequest($newRequest);
135
            $psrRequest = $psrRequest->withUri($psrRequest->getUri()->withPort(parse_url($url, PHP_URL_PORT)));
136
            $psrResponse = $this->proxy->forward($psrRequest)->to($this->getHostWithScheme($url));
137
            $response = $this->httpFoundationFactory->createResponse($psrResponse);
138
            $this->cleanResponseHeaders($response->headers);
139
            $this->transformationHandler->transformResponse(
140
                $api['apiName'],
141
                $api['endpoint'],
142
                $response,
143
                clone $response
144
            );
145
        } catch (ClientException $e) {
146
            $response = $e->getResponse();
147
        } catch (ServerException $serverException) {
148
            $response = $serverException->getResponse();
149
        }
150
151
        return $response;
152
    }
153
154
    /**
155
     * Removes some headers from the thirdparty API's response. These headers get always invalid by graviton's
156
     * forwarding and should therefore not be delivered to the client.
157
     *
158
     * @param HeaderBag $headers The headerbag holding the thirdparty API's response headers
159
     *
160
     * @return void
161
     */
162
    protected function cleanResponseHeaders(HeaderBag $headers)
163
    {
164
        $headers->remove('transfer-encoding'); // Chunked responses get not automatically re-chunked by graviton
165
        $headers->remove('trailer'); // Only for chunked responses, graviton should re-set this when chunking
166
    }
167
168
    /**
169
     * get schema info
170
     *
171
     * @param Request $request request
172
     *
173
     * @return Response
174
     */
175
    public function schemaAction(Request $request)
176
    {
177
        $api = $this->decideApiAndEndpoint($request->getUri());
178
        $this->registerProxySources($api['apiName']);
179
        $schema = $this->apiLoader->getEndpointSchema(urldecode($api['endpoint']));
180
        $schema = $this->transformationHandler->transformSchema(
181
            $api['apiName'],
182
            $api['endpoint'],
183
            $schema,
184
            clone $schema
185
        );
186
        $response = new Response(json_encode($schema), 200);
187
        $response->headers->set('Content-Type', 'application/json');
188
189
        return $this->templating->renderResponse(
190
            'GravitonCoreBundle:Main:index.json.twig',
191
            array ('response' => $response->getContent()),
192
            $response
193
        );
194
    }
195
196
    /**
197
     * get API name and endpoint from the url (third party API)
198
     *
199
     * @param string $url the url
200
     *
201
     * @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...
202
     */
203
    protected function decideApiAndEndpoint($url)
204
    {
205
        $path = parse_url($url, PHP_URL_PATH);
206
207
        $pattern = array (
208
            "@schema\/@",
209
            "@\/3rdparty\/@",
210
            "@\/item$@",
211
        );
212
        $path = preg_replace($pattern, '', $path);
213
214
        //get api name and endpoint
215
        $apiName = substr($path, 0, strpos($path, '/'));
216
        $endpoint = str_replace($apiName, '', $path);
217
218
        return array (
219
            "apiName" => $apiName,
220
            "endpoint" => $endpoint,
221
        );
222
    }
223
224
    /**
225
     * Registers configured external services to be proxied.
226
     *
227
     * @param string $apiPrefix The prefix of the API
228
     *
229
     * @return void
230
     */
231
    private function registerProxySources($apiPrefix = '')
232
    {
233
        if (array_key_exists('swagger', $this->proxySourceConfiguration)) {
234
            foreach ($this->proxySourceConfiguration['swagger'] as $config) {
235
                if ($apiPrefix == $config['prefix']) {
236
                    $this->apiLoader->setOption($config);
237
                    return;
238
                }
239
            }
240
        }
241
        $e = new NotFoundException('No such thirdparty API.');
242
        $e->setResponse(Response::create());
243
        throw $e;
244
    }
245
246
    /**
247
     * get host, scheme and port
248
     *
249
     * @param string $url the url
250
     *
251
     * @return string
252
     */
253
    private function getHostWithScheme($url)
254
    {
255
        $components = parse_url($url);
256
        $host = $components['scheme'].'://'.$components['host'];
257
        if (!empty($components['port'])) {
258
            $host .= ':'.$components['port'];
259
        }
260
261
        return $host;
262
    }
263
}
264