Browse Source

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.
Florian Orben 3 years ago
parent
commit
502c102ffc
No account linked to committer's email address

+ 2
- 0
README.md View File

@@ -619,6 +619,8 @@ You can enable the following middleware using the "middlewares" config parameter
619 619
 - "joinLimits": Restricts join parameters to prevent database scraping
620 620
 - "customization": Provides handlers for request and response customization
621 621
 - "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.
622 624
 
623 625
 The "middlewares" config parameter is a comma separated list of enabled middlewares.
624 626
 You can tune the middleware behavior using middleware specific configuration parameters:

+ 82
- 11
api.php View File

@@ -6893,6 +6893,17 @@ namespace Tqdev\PhpCrudApi\Middleware\Base {
6893 6893
             $this->properties = $properties;
6894 6894
         }
6895 6895
 
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
+
6896 6907
         protected function getArrayProperty(string $key, string $default): array
6897 6908
         {
6898 6909
             return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));
@@ -7056,6 +7067,11 @@ namespace Tqdev\PhpCrudApi\Middleware\Router {
7056 7067
                 $data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
7057 7068
                 $this->cache->set('PathTree', $data, $this->ttl);
7058 7069
             }
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
+
7059 7075
             return $this->handle($request);
7060 7076
         }
7061 7077
 
@@ -7376,6 +7392,55 @@ namespace Tqdev\PhpCrudApi\Middleware {
7376 7392
     }
7377 7393
 }
7378 7394
 
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
+
7379 7444
 // file: src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php
7380 7445
 namespace Tqdev\PhpCrudApi\Middleware {
7381 7446
 
@@ -7443,6 +7508,16 @@ namespace Tqdev\PhpCrudApi\Middleware {
7443 7508
             }
7444 7509
             return $response;
7445 7510
         }
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
+        }
7446 7521
     }
7447 7522
 }
7448 7523
 
@@ -10641,6 +10716,7 @@ namespace Tqdev\PhpCrudApi {
10641 10716
     use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
10642 10717
     use Tqdev\PhpCrudApi\Middleware\AuthorizationMiddleware;
10643 10718
     use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware;
10719
+    use Tqdev\PhpCrudApi\Middleware\CatchErrorsMiddleware;
10644 10720
     use Tqdev\PhpCrudApi\Middleware\CorsMiddleware;
10645 10721
     use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware;
10646 10722
     use Tqdev\PhpCrudApi\Middleware\DbAuthMiddleware;
@@ -10684,6 +10760,7 @@ namespace Tqdev\PhpCrudApi {
10684 10760
             $reflection = new ReflectionService($db, $cache, $config->getCacheTime());
10685 10761
             $responder = new JsonResponder();
10686 10762
             $router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
10763
+            new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
10687 10764
             foreach ($config->getMiddlewares() as $middleware => $properties) {
10688 10765
                 switch ($middleware) {
10689 10766
                     case 'sslRedirect':
@@ -10737,6 +10814,9 @@ namespace Tqdev\PhpCrudApi {
10737 10814
                     case 'xml':
10738 10815
                         new XmlMiddleware($router, $responder, $properties, $reflection);
10739 10816
                         break;
10817
+                    case 'errors':
10818
+                        new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
10819
+                        break;
10740 10820
                 }
10741 10821
             }
10742 10822
             foreach ($config->getControllers() as $controller) {
@@ -10833,16 +10913,7 @@ namespace Tqdev\PhpCrudApi {
10833 10913
 
10834 10914
         public function handle(ServerRequestInterface $request): ResponseInterface
10835 10915
         {
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));
10846 10917
         }
10847 10918
     }
10848 10919
 }
@@ -10860,7 +10931,7 @@ namespace Tqdev\PhpCrudApi {
10860 10931
             'password' => null,
10861 10932
             'database' => null,
10862 10933
             'tables' => '',
10863
-            'middlewares' => 'cors',
10934
+            'middlewares' => 'cors,errors',
10864 10935
             'controllers' => 'records,geojson,openapi',
10865 10936
             'customControllers' => '',
10866 10937
             'customOpenApiBuilders' => '',

+ 6
- 10
src/Tqdev/PhpCrudApi/Api.php View File

@@ -18,6 +18,7 @@ use Tqdev\PhpCrudApi\Database\GenericDB;
18 18
 use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
19 19
 use Tqdev\PhpCrudApi\Middleware\AuthorizationMiddleware;
20 20
 use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware;
21
+use Tqdev\PhpCrudApi\Middleware\CatchErrorsMiddleware;
21 22
 use Tqdev\PhpCrudApi\Middleware\CorsMiddleware;
22 23
 use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware;
23 24
 use Tqdev\PhpCrudApi\Middleware\DbAuthMiddleware;
@@ -61,6 +62,7 @@ class Api implements RequestHandlerInterface
61 62
         $reflection = new ReflectionService($db, $cache, $config->getCacheTime());
62 63
         $responder = new JsonResponder();
63 64
         $router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
65
+        new CatchErrorsMiddleware($router, $responder, [], $config->getDebug());
64 66
         foreach ($config->getMiddlewares() as $middleware => $properties) {
65 67
             switch ($middleware) {
66 68
                 case 'sslRedirect':
@@ -114,6 +116,9 @@ class Api implements RequestHandlerInterface
114 116
                 case 'xml':
115 117
                     new XmlMiddleware($router, $responder, $properties, $reflection);
116 118
                     break;
119
+                case 'errors':
120
+                    new CatchErrorsMiddleware($router, $responder, $properties, $config->getDebug());
121
+                    break;
117 122
             }
118 123
         }
119 124
         foreach ($config->getControllers() as $controller) {
@@ -210,15 +215,6 @@ class Api implements RequestHandlerInterface
210 215
 
211 216
     public function handle(ServerRequestInterface $request): ResponseInterface
212 217
     {
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));
223 219
     }
224 220
 }

+ 1
- 1
src/Tqdev/PhpCrudApi/Config.php View File

@@ -12,7 +12,7 @@ class Config
12 12
         'password' => null,
13 13
         'database' => null,
14 14
         'tables' => '',
15
-        'middlewares' => 'cors',
15
+        'middlewares' => 'cors,errors',
16 16
         'controllers' => 'records,geojson,openapi',
17 17
         'customControllers' => '',
18 18
         'customOpenApiBuilders' => '',

+ 11
- 0
src/Tqdev/PhpCrudApi/Middleware/Base/Middleware.php View File

@@ -19,6 +19,17 @@ abstract class Middleware implements MiddlewareInterface
19 19
         $this->properties = $properties;
20 20
     }
21 21
 
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
+
22 33
     protected function getArrayProperty(string $key, string $default): array
23 34
     {
24 35
         return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));

+ 49
- 0
src/Tqdev/PhpCrudApi/Middleware/CatchErrorsMiddleware.php View File

@@ -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
+}

+ 10
- 0
src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php View File

@@ -66,4 +66,14 @@ class CorsMiddleware extends Middleware
66 66
         }
67 67
         return $response;
68 68
     }
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
+    }
69 79
 }

+ 5
- 0
src/Tqdev/PhpCrudApi/Middleware/Router/SimpleRouter.php View File

@@ -95,6 +95,11 @@ class SimpleRouter implements Router
95 95
             $data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
96 96
             $this->cache->set('PathTree', $data, $this->ttl);
97 97
         }
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
+
98 103
         return $this->handle($request);
99 104
     }
100 105
 

Loading…
Cancel
Save