-
-
Notifications
You must be signed in to change notification settings - Fork 362
/
Application.php
174 lines (153 loc) · 5.28 KB
/
Application.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php
declare(strict_types=1);
namespace Bref;
use Bref\Bridge\Psr7\RequestFactory;
use Bref\Cli\InvokeCommand;
use Bref\Cli\WelcomeApplication;
use Bref\Http\LambdaResponse;
use Bref\Http\WelcomeHandler;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Filesystem\Filesystem;
use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\ServerRequestFactory;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Application
{
/**
* We should that directory to store the output file.
* See `writeLambdaOutput()`.
*/
private const BREF_DIRECTORY = '/tmp/.bref';
private const OUTPUT_FILE_NAME = self::BREF_DIRECTORY . '/output.json';
/**
* @var callable
*/
private $simpleHandler;
/**
* @var RequestHandlerInterface
*/
private $httpHandler;
/**
* @var \Symfony\Component\Console\Application
*/
private $cliHandler;
public function __construct()
{
// Define default "demo" handlers
$this->simpleHandler(function () {
return 'Welcome to Bref! Define your handler using $application->simpleHandler()';
});
$this->httpHandler(new WelcomeHandler);
$this->cliHandler(new WelcomeApplication);
}
/**
* Set the handler that will handle simple invocations of the lambda
* (through `serverless invoke` for example).
*
* @param callable $handler This callable takes a $event parameter (array) and must return anything serializable to JSON.
*/
public function simpleHandler(callable $handler) : void
{
$this->simpleHandler = $handler;
}
/**
* Set the handler that will handle HTTP requests.
*
* The handler must be a PSR-15 request handler, it can be any
* framework that is compatible with PSR-15 for example.
*/
public function httpHandler(RequestHandlerInterface $handler) : void
{
$this->httpHandler = $handler;
}
/**
* Set the handler that will handle CLI requests.
*
* CLI requests are local invocations of `bref cli <command>`:
* the command will be run in production remotely using this handler.
*
* The handler must be an instance of a Symfony Console application.
* That can also be a Silly (https://github.com/mnapoli/silly) application
* since Silly is based on the Symfony Console.
*/
public function cliHandler(\Symfony\Component\Console\Application $console) : void
{
// Necessary to avoid any `exit()` call :)
$console->setAutoExit(false);
$this->cliHandler = $console;
// Always add our `bref:invoke` command to test the lambda locally
$this->cliHandler->add(new InvokeCommand(function () {
return $this->simpleHandler;
}));
}
/**
* Run the application.
*
* The application will detect how the lambda is being invoked (HTTP,
* CLI, direct invocation, etc.) and execute the proper handler.
*/
public function run() : void
{
if (!$this->isRunningInAwsLambda()) {
if (php_sapi_name() == "cli") {
$this->cliHandler->setAutoExit(true);
$this->cliHandler->run();
} else {
$request = ServerRequestFactory::fromGlobals();
$response = $this->httpHandler->handle($request);
(new SapiEmitter)->emit($response);
}
return;
}
$this->ensureTempDirectoryExists();
$event = $this->readLambdaEvent();
// Run the appropriate handler
if (isset($event['httpMethod'])) {
// HTTP request
$request = RequestFactory::fromLambdaEvent($event);
$response = $this->httpHandler->handle($request);
$output = LambdaResponse::fromPsr7Response($response)->toJson();
} elseif (isset($event['cli'])) {
// CLI command
$cliInput = new StringInput($event['cli']);
$cliOutput = new BufferedOutput;
$exitCode = $this->cliHandler->run($cliInput, $cliOutput);
$output = json_encode([
'exitCode' => $exitCode,
'output' => $cliOutput->fetch(),
]);
} else {
// Simple invocation
$output = ($this->simpleHandler)($event);
$output = json_encode($output);
}
$this->writeLambdaOutput($output);
}
private function ensureTempDirectoryExists() : void
{
$filesystem = new Filesystem;
if (! $filesystem->exists(self::BREF_DIRECTORY)) {
$filesystem->mkdir(self::BREF_DIRECTORY);
}
}
private function readLambdaEvent() : array
{
// The lambda event is passed as JSON by `handler.js` as a CLI argument
global $argv;
return json_decode($argv[1], true) ?: [];
}
private function writeLambdaOutput(string $json) : void
{
file_put_contents(self::OUTPUT_FILE_NAME, $json);
}
private function isRunningInAwsLambda() : bool
{
// LAMBDA_TASK_ROOT is a constant defined by AWS
// TODO: use a solution that would work with other hosts?
return getenv('LAMBDA_TASK_ROOT') !== false;
}
}