a docker-inspired heroku clone in 100 lines of php
- php supervisor.php
- php -S 127.0.0.1:8080 app.php
- git archive --format=zip master > api-api.zip
- curl -XPOST 127.0.0.1:8080/api/api -F file='@api-api.zip'
<?php | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Process\Process; | |
require 'vendor/autoload.php'; | |
$app = new Silex\Application(); | |
$app['debug'] = true; | |
$app->get('/', function () { | |
return <<<HTML | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>API API</title> | |
</head> | |
<body> | |
<h1>API API</h1> | |
<p>This is an API for creating APIs. Just send a | |
<code>POST</code> request to /api/api with a zip | |
file attached containing your code under the form | |
key <code>file</code>.</p> | |
<p>We will then run your API!</p> | |
</body> | |
</html> | |
HTML; | |
}); | |
$app->post('/api/api', function (Request $request) use ($app) { | |
$appId = hash('sha1', randomBytes(512)); | |
$appDir = __DIR__.'/tmp/'.$appId; | |
if (!$request->files->has('file')) { | |
return $app->json(['error' => 'missing file'], 412); | |
} | |
if (!is_dir($appDir)) { | |
mkdir($appDir, 0755, true); | |
} | |
$file = $request->files->get('file'); | |
$file->move($appDir, '__upload.zip'); | |
run($appDir, cmd('unzip %s', '__upload.zip')); | |
run($appDir, cmd('rm %s', '__upload.zip')); | |
run($appDir, cmd('wget %s', 'https://getcomposer.org/composer.phar')); | |
run($appDir, cmd('php %s/composer.phar install', $appDir)); | |
$port = 8080; | |
do { | |
$port++; | |
$process = run(null, cmd('nc -z 127.0.0.1 %s', $port)); | |
} while ($process->isSuccessful()); | |
background($appDir, cmd('php -S %s app.php', '127.0.0.1:'.$port)); | |
return $app->json([ | |
'id' => $appId, | |
'port' => $port, | |
]); | |
}); | |
$app->run(); | |
function randomBytes($n) { | |
$fd = fopen('/dev/urandom', 'r'); | |
$bytes = fread($fd, $n); | |
fclose($fd); | |
return $bytes; | |
} | |
function run($cwd, $cmd) { | |
$process = new Process($cmd, $cwd); | |
$process->run(); | |
return $process; | |
} | |
function background($cwd, $cmd) { | |
$client = stream_socket_client('unix://supervisor.ipc', $errno, $errstr); | |
if (!$client) { | |
throw new Exception($errstr, $errno); | |
} | |
$data = [ | |
'cmd' => $cmd, | |
'cwd' => $cwd, | |
]; | |
fwrite($client, json_encode($data)); | |
} | |
function cmd($cmd, ...$args) { | |
return vsprintf($cmd, array_map('escapeshellarg', $args)); | |
} |
{ | |
"require": { | |
"silex/silex": "^1.0", | |
"symfony/process": "^3.0" | |
} | |
} |
<?php | |
use Symfony\Component\Process\Process; | |
require 'vendor/autoload.php'; | |
$server = stream_socket_server('unix://supervisor.ipc', $errno, $errstr); | |
if (!$server) { | |
throw new Exception($errstr, $errno); | |
} | |
while ($conn = stream_socket_accept($server, -1)) { | |
$request = stream_get_line($conn, 1024); | |
$data = json_decode($request, true); | |
echo $data['cmd']."\n"; | |
$process = new Process($data['cmd'], $data['cwd']); | |
$process->start(); | |
fclose($conn); | |
} | |
fclose($server); |
+1
👍
I don't think you get to claim 100 lines of code when you use Symfony but 👍 regardless. PHP in 2015 has changed a lot from PHP in 2010, hasn't it?
Interesting.
🎱
cool
I now blame Amanda for this.
looks great
I accept your blame.