Completed
Pull Request — develop (#535)
by Bastian
14:26 queued 09:46
created

ProxyController::buildRequest()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 0
cts 25
cp 0
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 21
nc 1
nop 3
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\Source\Registry;
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 Registry
61
     */
62
    private $registry;
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 Registry              $registry                 Registry of proxy sources
79
     */
80
    public function __construct(
81
        Proxy $proxy,
82
        EngineInterface $templating,
83
        ApiDefinitionLoader $loader,
84
        DiactorosFactory $diactorosFactory,
85
        HttpFoundationFactory $httpFoundationFactory,
86
        TransformationHandler $transformationHandler,
87
        Registry $registry
88
    ) {
89
        $this->proxy = $proxy;
90
        $this->templating = $templating;
91
        $this->apiLoader = $loader;
92
        $this->diactorosFactory = $diactorosFactory;
93
        $this->httpFoundationFactory = $httpFoundationFactory;
94
        $this->registry = $registry;
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->registerProxySource($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
            $psrRequest = $this->buildRequest($request, $url, $api);
119
            $psrResponse = $this->proxy->forward($psrRequest)->to($this->getHostWithScheme($url));
0 ignored issues
show
Documentation introduced by
$psrRequest is of type object<Psr\Http\Message\ResponseInterface>, but the function expects a object<Psr\Http\Message\RequestInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
120
            $response = $this->httpFoundationFactory->createResponse($psrResponse);
121
            $this->cleanResponseHeaders($response->headers);
122
            $this->transformationHandler->transformResponse(
123
                $api['apiName'],
124
                $api['endpoint'],
125
                $response,
126
                clone $response
127
            );
128
        } catch (ClientException $e) {
129
            $response = $e->getResponse();
130
        } catch (ServerException $serverException) {
131
            $response = $serverException->getResponse();
132
        } catch (RequestException $e) {
133
            $message = json_encode(
134
                ['code' => 404, 'message' => 'HTTP 404 Not found']
135
            );
136
137
            throw new NotFoundHttpException($message, $e);
138
        }
139
140
        return $response;
141
    }
142
143
    /**
144
     * Removes some headers from the thirdparty API's response. These headers get always invalid by graviton's
145
     * forwarding and should therefore not be delivered to the client.
146
     *
147
     * @param HeaderBag $headers The headerbag holding the thirdparty API's response headers
148
     *
149
     * @return void
150
     */
151
    protected function cleanResponseHeaders(HeaderBag $headers)
152
    {
153
        $headers->remove('transfer-encoding'); // Chunked responses get not automatically re-chunked by graviton
154
        $headers->remove('trailer'); // Only for chunked responses, graviton should re-set this when chunking
155
156
        // Change naming to Graviton output Allow options
157
        if ($allow = $headers->get('Allow', false)) {
158
            $headers->set('Access-Control-Allow-Methods', $allow);
159
            $headers->remove('Allow');
160
        }
161
    }
162
163
    /**
164
     * get schema info
165
     *
166
     * @param Request $request request
167
     *
168
     * @return Response
169
     */
170
    public function schemaAction(Request $request)
171
    {
172
        $api = $this->decideApiAndEndpoint($request->getUri());
173
        $this->registerProxySource($api['apiName']);
174
        $this->apiLoader->addOptions($api);
175
176
        $schema = $this->apiLoader->getEndpointSchema(urldecode($api['endpoint']));
177
        $schema = $this->transformationHandler->transformSchema(
178
            $api['apiName'],
179
            $api['endpoint'],
180
            $schema,
181
            clone $schema
182
        );
183
        $response = new Response(json_encode($schema), 200);
184
        $response->headers->set('Content-Type', 'application/json');
185
186
        return $this->templating->renderResponse(
187
            'GravitonCoreBundle:Main:index.json.twig',
188
            array ('response' => $response->getContent()),
189
            $response
190
        );
191
    }
192
193
    /**
194
     * get API name and endpoint from the url (third party API)
195
     *
196
     * @param string $url the url
197
     *
198
     * @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...
199
     */
200
    protected function decideApiAndEndpoint($url)
201
    {
202
        $path = parse_url($url, PHP_URL_PATH);
203
204
        $pattern = array (
205
            "@schema\/@",
206
            "@\/3rdparty\/@",
207
            "@\/item$@",
208
        );
209
        $path = preg_replace($pattern, '', $path);
210
211
        //get api name and endpoint
212
        $apiName = substr($path, 0, strpos($path, '/'));
213
        $endpoint = str_replace($apiName, '', $path);
214
215
        return array (
216
            "apiName" => $apiName,
217
            "endpoint" => $endpoint,
218
        );
219
    }
220
221
    /**
222
     * Registers configured external services to be proxied.
223
     *
224
     * @param string $apiPrefix The prefix of the API
225
     *
226
     * @return void
227
     */
228
    private function registerProxySource($apiPrefix = '')
229
    {
230
        if ($this->registry->has($apiPrefix)) {
231
            $this->apiLoader->setOption($config);
232
            return;
233
        }
234
235
        $e = new NotFoundException('No such thirdparty API.');
236
        $e->setResponse(Response::create());
237
        throw $e;
238
    }
239
240
    /**
241
     * get host, scheme and port
242
     *
243
     * @param string $url the url
244
     *
245
     * @return string
246
     */
247
    private function getHostWithScheme($url)
248
    {
249
        $components = parse_url($url);
250
        $host = $components['scheme'].'://'.$components['host'];
251
        if (!empty($components['port'])) {
252
            $host .= ':'.$components['port'];
253
        }
254
255
        return $host;
256
    }
257
258
    /**
259
     * @param Request $request
260
     * @param string  $url
261
     * @param array   $api
262
     *
263
     * @return \Psr\Http\Message\ResponseInterface
264
     */
265
    private function buildRequest(Request $request, $url, array $api)
266
    {
267
        $newRequest = Request::create(
268
            $url,
269
            $request->getMethod(),
270
            array(),
271
            array(),
272
            array(),
273
            array(),
274
            $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...
275
        );
276
        $newRequest->headers->add($request->headers->all());
277
        $newRequest->query->add($request->query->all());
278
        $queryString = $request->server->get('QUERY_STRING');
279
        $newRequest->server->set('QUERY_STRING', $queryString);
280
281
        $newRequest = $this->transformationHandler->transformRequest(
282
            $api['apiName'],
283
            $api['endpoint'],
284
            $request,
285
            $newRequest
286
        );
287
        /** @var \Psr\Http\Message\ResponseInterface $psrRequest */
288
        $psrRequest = $this->diactorosFactory->createRequest($newRequest);
289
        $psrRequest = $psrRequest->withUri($psrRequest->getUri()->withPort(parse_url($url, PHP_URL_PORT)));
0 ignored issues
show
Bug introduced by
The method getUri() does not seem to exist on object<Psr\Http\Message\ResponseInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method withUri() does not seem to exist on object<Psr\Http\Message\ResponseInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
290
291
        return $psrRequest;
292
    }
293
}
294