Skip to content

Commit 502c102

Browse files
authored
CORS headers not sent when an exception is thrown (#711)
* CORS headers not sent when an exception is thrown Replaces error handling in API@handle with a middleware that will catch throwables. If applied after cors middleware, CORS headers will be enabled for error messages. * CORS headers not sent when an exception is thrown Replaces error handling in API@handle with a middleware that will catch throwables. If applied after cors middleware, CORS headers will be enabled for error messages.
1 parent 220a7d7 commit 502c102

File tree

8 files changed

+166
-22
lines changed

8 files changed

+166
-22
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ You can enable the following middleware using the "middlewares" config parameter
619619
- "joinLimits": Restricts join parameters to prevent database scraping
620620
- "customization": Provides handlers for request and response customization
621621
- "xml": Translates all input and output from JSON to XML
622+
- "errors": Catches throwables and returns an error response instead of throwing (enabled by default)\
623+
Should always be applied after cors middleware, otherwise errors will not have CORS headers sent to the client.
622624

623625
The "middlewares" config parameter is a comma separated list of enabled middlewares.
624626
You can tune the middleware behavior using middleware specific configuration parameters:

api.php

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6893,6 +6893,17 @@ public function __construct(Router $router, Responder $responder, array $propert
68936893
$this->properties = $properties;
68946894
}
68956895

6896+
/**
6897+
* allows to load middlewares in a specific order
6898+
* The higher the priority, the earlier the middleware will be called
6899+
*
6900+
* @return int
6901+
*/
6902+
public function getPriority() /* : int */
6903+
{
6904+
return 1;
6905+
}
6906+
68966907
protected function getArrayProperty(string $key, string $default): array
68976908
{
68986909
return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));
@@ -7056,6 +7067,11 @@ public function route(ServerRequestInterface $request): ResponseInterface
70567067
$data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
70577068
$this->cache->set('PathTree', $data, $this->ttl);
70587069
}
7070+
7071+
uasort($this->middlewares, function (Middleware $a, Middleware $b) {
7072+
return $a->getPriority() > $b->getPriority() ? 1 : ($a->getPriority() === $b->getPriority() ? 0 : -1);
7073+
});
7074+
70597075
return $this->handle($request);
70607076
}
70617077

@@ -7376,6 +7392,55 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
73767392
}
73777393
}
73787394

7395+
// file: src/Tqdev/PhpCrudApi/Middleware/CatchErrorsMiddleware.php
7396+
namespace Tqdev\PhpCrudApi\Middleware {
7397+
7398+
use Psr\Http\Message\ResponseInterface;
7399+
use Psr\Http\Message\ServerRequestInterface;
7400+
use Psr\Http\Server\RequestHandlerInterface;
7401+
use Tqdev\PhpCrudApi\Controller\Responder;
7402+
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
7403+
use Tqdev\PhpCrudApi\Middleware\Router\Router;
7404+
use Tqdev\PhpCrudApi\Record\ErrorCode;
7405+
use Tqdev\PhpCrudApi\ResponseUtils;
7406+
7407+
class CatchErrorsMiddleware extends Middleware
7408+
{
7409+
private $debug;
7410+
7411+
public function __construct(Router $router, Responder $responder, array $properties, bool $debug)
7412+
{
7413+
parent::__construct($router, $responder, $properties);
7414+
$this->debug = $debug;
7415+
}
7416+
7417+
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
7418+
{
7419+
$response = null;
7420+
try {
7421+
$response = $next->handle($request);
7422+
} catch (\Throwable $e) {
7423+
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
7424+
if ($this->debug) {
7425+
$response = ResponseUtils::addExceptionHeaders($response, $e);
7426+
}
7427+
}
7428+
return $response;
7429+
}
7430+
7431+
/**
7432+
* High priority, should always be one of the very first middlewares to be loaded
7433+
* Only cors middleware should be loaded earlier
7434+
*
7435+
* @return int
7436+
*/
7437+
public function getPriority()
7438+
{
7439+
return 998;
7440+
}
7441+
}
7442+
}
7443+
73797444
// file: src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php
73807445
namespace Tqdev\PhpCrudApi\Middleware {
73817446

@@ -7443,6 +7508,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
74437508
}
74447509
return $response;
74457510
}
7511+
7512+
/**
7513+
* load early in the routing stack. should be loaded before catc herrors middleware,
7514+
* otherwise cors headers will be missing
7515+
* @return int
7516+
*/
7517+
public function getPriority()
7518+
{
7519+
return 999;
7520+
}
74467521
}
74477522
}
74487523

@@ -10641,6 +10716,7 @@ private function setHabtmValues(ReflectedTable $t1, ReflectedTable $t2, array &$
1064110716
use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
1064210717
use Tqdev\PhpCrudApi\Middleware\AuthorizationMiddleware;
1064310718
use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware;
10719+
use Tqdev\PhpCrudApi\Middleware\CatchErrorsMiddleware;
1064410720
use Tqdev\PhpCrudApi\Middleware\CorsMiddleware;
1064510721
use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware;
1064610722
use Tqdev\PhpCrudApi\Middleware\DbAuthMiddleware;
@@ -10684,6 +10760,7 @@ public function __construct(Config $config)
1068410760
$reflection = new ReflectionService($db, $cache, $config->getCacheTime());
1068510761
$responder = new JsonResponder();
1068610762
$router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
10763+
new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
1068710764
foreach ($config->getMiddlewares() as $middleware => $properties) {
1068810765
switch ($middleware) {
1068910766
case 'sslRedirect':
@@ -10737,6 +10814,9 @@ public function __construct(Config $config)
1073710814
case 'xml':
1073810815
new XmlMiddleware($router, $responder, $properties, $reflection);
1073910816
break;
10817+
case 'errors':
10818+
new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
10819+
break;
1074010820
}
1074110821
}
1074210822
foreach ($config->getControllers() as $controller) {
@@ -10833,16 +10913,7 @@ private function applyParsedBodyHack(ServerRequestInterface $request): ServerReq
1083310913

1083410914
public function handle(ServerRequestInterface $request): ResponseInterface
1083510915
{
10836-
$response = null;
10837-
try {
10838-
$response = $this->router->route($this->addParsedBody($request));
10839-
} catch (\Throwable $e) {
10840-
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
10841-
if ($this->debug) {
10842-
$response = ResponseUtils::addExceptionHeaders($response, $e);
10843-
}
10844-
}
10845-
return $response;
10916+
return $this->router->route($this->addParsedBody($request));
1084610917
}
1084710918
}
1084810919
}
@@ -10860,7 +10931,7 @@ class Config
1086010931
'password' => null,
1086110932
'database' => null,
1086210933
'tables' => '',
10863-
'middlewares' => 'cors',
10934+
'middlewares' => 'cors,errors',
1086410935
'controllers' => 'records,geojson,openapi',
1086510936
'customControllers' => '',
1086610937
'customOpenApiBuilders' => '',

src/Tqdev/PhpCrudApi/Api.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
1919
use Tqdev\PhpCrudApi\Middleware\AuthorizationMiddleware;
2020
use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware;
21+
use Tqdev\PhpCrudApi\Middleware\CatchErrorsMiddleware;
2122
use Tqdev\PhpCrudApi\Middleware\CorsMiddleware;
2223
use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware;
2324
use Tqdev\PhpCrudApi\Middleware\DbAuthMiddleware;
@@ -61,6 +62,7 @@ public function __construct(Config $config)
6162
$reflection = new ReflectionService($db, $cache, $config->getCacheTime());
6263
$responder = new JsonResponder();
6364
$router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
65+
new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
6466
foreach ($config->getMiddlewares() as $middleware => $properties) {
6567
switch ($middleware) {
6668
case 'sslRedirect':
@@ -114,6 +116,9 @@ public function __construct(Config $config)
114116
case 'xml':
115117
new XmlMiddleware($router, $responder, $properties, $reflection);
116118
break;
119+
case 'errors':
120+
new CatchErrorsMiddleware($router, $responder, $properties, $config->getDebug());
121+
break;
117122
}
118123
}
119124
foreach ($config->getControllers() as $controller) {
@@ -210,15 +215,6 @@ private function applyParsedBodyHack(ServerRequestInterface $request): ServerReq
210215

211216
public function handle(ServerRequestInterface $request): ResponseInterface
212217
{
213-
$response = null;
214-
try {
215-
$response = $this->router->route($this->addParsedBody($request));
216-
} catch (\Throwable $e) {
217-
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
218-
if ($this->debug) {
219-
$response = ResponseUtils::addExceptionHeaders($response, $e);
220-
}
221-
}
222-
return $response;
218+
return $this->router->route($this->addParsedBody($request));
223219
}
224220
}

src/Tqdev/PhpCrudApi/Config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Config
1212
'password' => null,
1313
'database' => null,
1414
'tables' => '',
15-
'middlewares' => 'cors',
15+
'middlewares' => 'cors,errors',
1616
'controllers' => 'records,geojson,openapi',
1717
'customControllers' => '',
1818
'customOpenApiBuilders' => '',

src/Tqdev/PhpCrudApi/Middleware/Base/Middleware.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ public function __construct(Router $router, Responder $responder, array $propert
1919
$this->properties = $properties;
2020
}
2121

22+
/**
23+
* allows to load middlewares in a specific order
24+
* The higher the priority, the earlier the middleware will be called
25+
*
26+
* @return int
27+
*/
28+
public function getPriority() /* : int */
29+
{
30+
return 1;
31+
}
32+
2233
protected function getArrayProperty(string $key, string $default): array
2334
{
2435
return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Tqdev\PhpCrudApi\Middleware;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
use Psr\Http\Server\RequestHandlerInterface;
8+
use Tqdev\PhpCrudApi\Controller\Responder;
9+
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
10+
use Tqdev\PhpCrudApi\Middleware\Router\Router;
11+
use Tqdev\PhpCrudApi\Record\ErrorCode;
12+
use Tqdev\PhpCrudApi\ResponseUtils;
13+
14+
15+
class CatchErrorsMiddleware extends Middleware
16+
{
17+
private $debug;
18+
19+
public function __construct(Router $router, Responder $responder, array $properties, bool $debug)
20+
{
21+
parent::__construct($router, $responder, $properties);
22+
$this->debug = $debug;
23+
}
24+
25+
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
26+
{
27+
$response = null;
28+
try {
29+
$response = $next->handle($request);
30+
} catch (\Throwable $e) {
31+
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
32+
if ($this->debug) {
33+
$response = ResponseUtils::addExceptionHeaders($response, $e);
34+
}
35+
}
36+
return $response;
37+
}
38+
39+
/**
40+
* High priority, should always be one of the very first middlewares to be loaded
41+
* Only cors middleware should be loaded earlier
42+
*
43+
* @return int
44+
*/
45+
public function getPriority()
46+
{
47+
return 998;
48+
}
49+
}

src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,14 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
6666
}
6767
return $response;
6868
}
69+
70+
/**
71+
* load early in the routing stack. should be loaded before catc herrors middleware,
72+
* otherwise cors headers will be missing
73+
* @return int
74+
*/
75+
public function getPriority()
76+
{
77+
return 999;
78+
}
6979
}

src/Tqdev/PhpCrudApi/Middleware/Router/SimpleRouter.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ public function route(ServerRequestInterface $request): ResponseInterface
9595
$data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
9696
$this->cache->set('PathTree', $data, $this->ttl);
9797
}
98+
99+
uasort($this->middlewares, function (Middleware $a, Middleware $b) {
100+
return $a->getPriority() > $b->getPriority() ? 1 : ($a->getPriority() === $b->getPriority() ? 0 : -1);
101+
});
102+
98103
return $this->handle($request);
99104
}
100105

0 commit comments

Comments
 (0)