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) |
|
|
|
|
122
|
|
|
); |
123
|
|
|
$newRequest->headers->add($request->headers->all()); |
124
|
|
|
|
125
|
|
|
$newRequest = $this->transformationHandler->transformRequest( |
126
|
|
|
$api['apiName'], |
127
|
|
|
$api['endpoint'], |
128
|
|
|
$request, |
129
|
|
|
$newRequest |
130
|
|
|
); |
131
|
|
|
$psrRequest = $this->diactorosFactory->createRequest($newRequest); |
132
|
|
|
$psrRequest = $psrRequest->withUri($psrRequest->getUri()->withPort(parse_url($url, PHP_URL_PORT))); |
133
|
|
|
$psrResponse = $this->proxy->forward($psrRequest)->to($this->getHostWithScheme($url)); |
134
|
|
|
$response = $this->httpFoundationFactory->createResponse($psrResponse); |
135
|
|
|
$this->cleanResponseHeaders($response->headers); |
136
|
|
|
$this->transformationHandler->transformResponse( |
137
|
|
|
$api['apiName'], |
138
|
|
|
$api['endpoint'], |
139
|
|
|
$response, |
140
|
|
|
clone $response |
141
|
|
|
); |
142
|
|
|
} catch (ClientException $e) { |
143
|
|
|
$response = $e->getResponse(); |
144
|
|
|
} catch (ServerException $serverException) { |
145
|
|
|
$response = $serverException->getResponse(); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
return $response; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Removes some headers from the thirdparty API's response. These headers get always invalid by graviton's |
153
|
|
|
* forwarding and should therefore not be delivered to the client. |
154
|
|
|
* |
155
|
|
|
* @param HeaderBag $headers The headerbag holding the thirdparty API's response headers |
156
|
|
|
* |
157
|
|
|
* @return void |
158
|
|
|
*/ |
159
|
|
|
protected function cleanResponseHeaders(HeaderBag $headers) |
160
|
|
|
{ |
161
|
|
|
$headers->remove('transfer-encoding'); // Chunked responses get not automatically re-chunked by graviton |
162
|
|
|
$headers->remove('trailer'); // Only for chunked responses, graviton should re-set this when chunking |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* get schema info |
167
|
|
|
* |
168
|
|
|
* @param Request $request request |
169
|
|
|
* |
170
|
|
|
* @return Response |
171
|
|
|
*/ |
172
|
|
|
public function schemaAction(Request $request) |
173
|
|
|
{ |
174
|
|
|
$api = $this->decideApiAndEndpoint($request->getUri()); |
175
|
|
|
$this->registerProxySources($api['apiName']); |
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 |
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 registerProxySources($apiPrefix = '') |
229
|
|
|
{ |
230
|
|
|
if (array_key_exists('swagger', $this->proxySourceConfiguration)) { |
231
|
|
|
foreach ($this->proxySourceConfiguration['swagger'] as $config) { |
232
|
|
|
if ($apiPrefix == $config['prefix']) { |
233
|
|
|
$this->apiLoader->setOption($config); |
234
|
|
|
return; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
$e = new NotFoundException('No such thirdparty API.'); |
239
|
|
|
$e->setResponse(Response::create()); |
240
|
|
|
throw $e; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* get host, scheme and port |
245
|
|
|
* |
246
|
|
|
* @param string $url the url |
247
|
|
|
* |
248
|
|
|
* @return string |
249
|
|
|
*/ |
250
|
|
|
private function getHostWithScheme($url) |
251
|
|
|
{ |
252
|
|
|
$components = parse_url($url); |
253
|
|
|
$host = $components['scheme'].'://'.$components['host']; |
254
|
|
|
if (!empty($components['port'])) { |
255
|
|
|
$host .= ':'.$components['port']; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return $host; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
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.