Skip to content

Commit 66844c4

Browse files
[13.x] Handle api / json routes with Down (Maintenance) command (#60595)
* Updated down command and middleware to handle ignore. * Style fixes * allow framework to handle json requests on pre-rendered maintenance stubs --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 40efd71 commit 66844c4

3 files changed

Lines changed: 84 additions & 2 deletions

File tree

src/Illuminate/Foundation/Console/stubs/maintenance-mode.stub

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ if (! file_exists($down = __DIR__.'/down')) {
88
// Decode the "down" file's JSON...
99
$data = json_decode(file_get_contents($down), true);
1010

11+
$expectsJson = (function () {
12+
$acceptable = array_map('trim', explode(',', $_SERVER['HTTP_ACCEPT'] ?? ''));
13+
14+
$firstAcceptable = strtolower(strtok($acceptable[0] ?? '', ';'));
15+
16+
if (str_contains($firstAcceptable, '/json') || str_contains($firstAcceptable, '+json')) {
17+
return true;
18+
}
19+
20+
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
21+
strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') === 0 &&
22+
(! isset($_SERVER['HTTP_X_PJAX']) || strcasecmp($_SERVER['HTTP_X_PJAX'], 'true') !== 0) &&
23+
in_array($firstAcceptable, ['', '*/*', '*'], true);
24+
})();
25+
1126
// Allow framework to handle request if no prerendered template...
1227
if (! isset($data['template'])) {
1328
return;
@@ -54,6 +69,11 @@ if (isset($_COOKIE['laravel_maintenance']) && isset($data['secret'])) {
5469
}
5570
}
5671

72+
// Allow framework to handle requests expecting a JSON response...
73+
if ($expectsJson) {
74+
return;
75+
}
76+
5777
// Redirect to the proper path if necessary...
5878
if (isset($data['redirect']) && $_SERVER['REQUEST_URI'] !== $data['redirect']) {
5979
http_response_code(302);

src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function handle($request, Closure $next)
8080
return $next($request);
8181
}
8282

83-
if (isset($data['redirect'])) {
83+
if (isset($data['redirect']) && ! $request->expectsJson()) {
8484
$path = $data['redirect'] === '/'
8585
? $data['redirect']
8686
: trim($data['redirect'], '/');
@@ -90,7 +90,7 @@ public function handle($request, Closure $next)
9090
}
9191
}
9292

93-
if (isset($data['template'])) {
93+
if (isset($data['template']) && ! $request->expectsJson()) {
9494
return response(
9595
$data['template'],
9696
$data['status'] ?? 503,

tests/Integration/Foundation/MaintenanceModeTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ protected function setUp(): void
2222
{
2323
$this->beforeApplicationDestroyed(function () {
2424
@unlink(storage_path('framework/down'));
25+
@unlink(storage_path('framework/maintenance.php'));
2526
});
2627

2728
parent::setUp();
@@ -80,6 +81,67 @@ public function testMaintenanceModeCanHaveCustomTemplate()
8081
$this->assertSame('Rendered Content', $response->original);
8182
}
8283

84+
public function testMaintenanceModeDoesNotUseCustomTemplateForJsonRequests()
85+
{
86+
file_put_contents(storage_path('framework/down'), json_encode([
87+
'retry' => 60,
88+
'template' => 'Rendered Content',
89+
]));
90+
91+
Route::get('/foo', function () {
92+
return 'Hello World';
93+
})->middleware(PreventRequestsDuringMaintenance::class);
94+
95+
$response = $this->getJson('/foo');
96+
97+
$response->assertStatus(503);
98+
$response->assertHeader('Retry-After', '60');
99+
$response->assertJson(['message' => 'Service Unavailable']);
100+
}
101+
102+
public function testMaintenanceModeDoesNotRedirectJsonRequests()
103+
{
104+
file_put_contents(storage_path('framework/down'), json_encode([
105+
'redirect' => '/maintenance',
106+
]));
107+
108+
Route::get('/foo', function () {
109+
return 'Hello World';
110+
})->middleware(PreventRequestsDuringMaintenance::class);
111+
112+
$response = $this->getJson('/foo');
113+
114+
$response->assertStatus(503);
115+
$response->assertJson(['message' => 'Service Unavailable']);
116+
}
117+
118+
public function testPrerenderedMaintenanceFileAllowsJsonRequestsToReachFramework()
119+
{
120+
file_put_contents(storage_path('framework/down'), json_encode([
121+
'template' => 'Rendered Content',
122+
]));
123+
124+
file_put_contents(
125+
storage_path('framework/maintenance.php'),
126+
file_get_contents(__DIR__.'/../../../src/Illuminate/Foundation/Console/stubs/maintenance-mode.stub')
127+
);
128+
129+
$server = $_SERVER;
130+
131+
try {
132+
$_SERVER['REQUEST_URI'] = '/foo';
133+
$_SERVER['HTTP_ACCEPT'] = 'application/json';
134+
135+
ob_start();
136+
include storage_path('framework/maintenance.php');
137+
$output = ob_get_clean();
138+
} finally {
139+
$_SERVER = $server;
140+
}
141+
142+
$this->assertSame('', $output);
143+
}
144+
83145
public function testMaintenanceModeCanRedirectWithBypassCookie()
84146
{
85147
file_put_contents(storage_path('framework/down'), json_encode([

0 commit comments

Comments
 (0)