This commit is contained in:
Marcel Pociot
2020-04-25 11:42:40 +02:00
parent c89f6b38ea
commit 28c4009dff
16 changed files with 81 additions and 74 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@
.phpunit.result.cache .phpunit.result.cache
expose.php expose.php
database/expose.db database/expose.db
.expose.php

View File

@@ -41,7 +41,9 @@ class Client
{ {
$token = config('expose.auth_token'); $token = config('expose.auth_token');
connect("ws://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__?authToken={$token}", [], [ $protocol = $this->configuration->port() === 443 ? "wss" : "ws";
connect($protocol."://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__?authToken={$token}", [], [
'X-Expose-Control' => 'enabled', 'X-Expose-Control' => 'enabled',
], $this->loop) ], $this->loop)
->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain) { ->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain) {
@@ -65,8 +67,10 @@ class Client
static::$subdomains[] = "$data->subdomain.{$this->configuration->host()}:{$this->configuration->port()}"; static::$subdomains[] = "$data->subdomain.{$this->configuration->host()}:{$this->configuration->port()}";
}); });
}, function ($e) { }, function (\Exception $e) {
echo "Could not connect: {$e->getMessage()}\n"; $this->logger->error("Could not connect to the server.");
$this->logger->error($e->getMessage());
exit(1);
}); });
} }
} }

View File

@@ -34,6 +34,6 @@ class Configuration
public function port(): int public function port(): int
{ {
return $this->port; return intval($this->port);
} }
} }

View File

@@ -24,7 +24,9 @@ class ProxyManager
public function createProxy(string $clientId, $connectionData) public function createProxy(string $clientId, $connectionData)
{ {
connect("ws://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__", [], [ $protocol = $this->configuration->port() === 443 ? "wss" : "ws";
connect($protocol."://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__", [], [
'X-Expose-Control' => 'enabled', 'X-Expose-Control' => 'enabled',
], $this->loop) ], $this->loop)
->then(function (WebSocket $proxyConnection) use ($clientId, $connectionData) { ->then(function (WebSocket $proxyConnection) use ($clientId, $connectionData) {

View File

@@ -30,8 +30,8 @@ class ShareCommand extends Command
(new Factory()) (new Factory())
->setLoop(app(LoopInterface::class)) ->setLoop(app(LoopInterface::class))
// ->setHost('beyond.sh') // TODO: Read from (local/global) config file ->setHost(config('expose.host', 'localhost'))
// ->setPort(8080) // TODO: Read from (local/global) config file ->setPort(config('expose.port', 8080))
->setAuth($this->option('auth')) ->setAuth($this->option('auth'))
->createClient($this->argument('host'), explode(',', $this->option('subdomain'))) ->createClient($this->argument('host'), explode(',', $this->option('subdomain')))
->createHttpServer() ->createHttpServer()

View File

@@ -12,6 +12,8 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
{ {
$this->input->setArgument('host', basename(getcwd()).'.test'); $this->input->setArgument('host', basename(getcwd()).'.test');
$this->input->setOption('subdomain', basename(getcwd()));
parent::handle(); parent::handle();
} }
} }

View File

@@ -5,6 +5,9 @@ namespace App\HttpServer\Controllers;
use Exception; use Exception;
use Ratchet\ConnectionInterface; use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface; use Ratchet\Http\HttpServerInterface;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use function GuzzleHttp\Psr7\stream_for;
abstract class Controller implements HttpServerInterface abstract class Controller implements HttpServerInterface
{ {
@@ -22,4 +25,17 @@ abstract class Controller implements HttpServerInterface
public function onMessage(ConnectionInterface $from, $msg) public function onMessage(ConnectionInterface $from, $msg)
{ {
} }
protected function getView(string $view, array $data)
{
$templatePath = implode(DIRECTORY_SEPARATOR, explode('.', $view));
$twig = new Environment(
new ArrayLoader([
'template' => file_get_contents(base_path('resources/views/'.$templatePath.'.twig')),
])
);
return stream_for($twig->render('template', $data));
}
} }

View File

@@ -16,19 +16,12 @@ class DashboardController extends Controller
str(new Response( str(new Response(
200, 200,
['Content-Type' => 'text/html'], ['Content-Type' => 'text/html'],
$this->getView() $this->getView('client.dashboard', [
'subdomains' => Client::$subdomains,
])
)) ))
); );
$connection->close(); $connection->close();
} }
protected function getView(): string
{
$view = file_get_contents(base_path('resources/views/index.html'));
$view = str_replace('%subdomains%', implode(' ', Client::$subdomains), $view);
return $view;
}
} }

View File

@@ -31,6 +31,14 @@ class AppServiceProvider extends ServiceProvider
protected function loadConfigurationFile() protected function loadConfigurationFile()
{ {
$localConfigFile = getcwd() . DIRECTORY_SEPARATOR . '.expose.php';
if (file_exists($localConfigFile)) {
config()->set('expose', require_once $localConfigFile);
return;
}
$configFile = implode(DIRECTORY_SEPARATOR, [ $configFile = implode(DIRECTORY_SEPARATOR, [
$_SERVER['HOME'], $_SERVER['HOME'],
'.expose', '.expose',
@@ -39,12 +47,6 @@ class AppServiceProvider extends ServiceProvider
if (file_exists($configFile)) { if (file_exists($configFile)) {
config()->set('expose', require_once $configFile); config()->set('expose', require_once $configFile);
return;
}
$localConfigFile = getcwd() . DIRECTORY_SEPARATOR . '.expose.php';
if (file_exists($localConfigFile)) {
config()->set('expose', require_once $localConfigFile);
} }
} }
} }

View File

@@ -80,9 +80,12 @@ class Factory
protected function addExposeRoutes() protected function addExposeRoutes()
{ {
$wsServer = new WsServer(app(ControlMessageController::class));
$wsServer->enableKeepAlive($this->loop);
$this->routes->add('control', $this->routes->add('control',
new Route('/__expose_control__', [ new Route('/__expose_control__', [
'_controller' => new WsServer(app(ControlMessageController::class)) '_controller' => $wsServer
], [], [], null, [], [] ], [], [], null, [], []
) )
); );

View File

@@ -27,18 +27,7 @@ class ListSitesController extends PostController
public function handle(Request $request, ConnectionInterface $httpConnection) public function handle(Request $request, ConnectionInterface $httpConnection)
{ {
$httpConnection->send( $httpConnection->send(
respond_html($this->getView(['sites' => $this->connectionManager->getConnections()])) respond_html($this->getView('server.sites.index', ['sites' => $this->connectionManager->getConnections()]))
); );
} }
protected function getView(array $data)
{
$twig = new Environment(
new ArrayLoader([
'template' => file_get_contents(base_path('resources/views/admin/sites/index.twig')),
])
);
return stream_for($twig->render('template', $data));
}
} }

View File

@@ -29,7 +29,7 @@ class ListUsersController extends PostController
{ {
$this->database->query('SELECT * FROM users ORDER by created_at DESC')->then(function (Result $result) use ($httpConnection) { $this->database->query('SELECT * FROM users ORDER by created_at DESC')->then(function (Result $result) use ($httpConnection) {
$httpConnection->send( $httpConnection->send(
respond_html($this->getView(['users' => $result->rows])) respond_html($this->getView('server.users.index', ['users' => $result->rows]))
); );
$httpConnection->close(); $httpConnection->close();
@@ -39,15 +39,4 @@ class ListUsersController extends PostController
$httpConnection->close(); $httpConnection->close();
}); });
} }
protected function getView(array $data)
{
$twig = new Environment(
new ArrayLoader([
'template' => file_get_contents(base_path('resources/views/admin/users/index.twig')),
])
);
return stream_for($twig->render('template', $data));
}
} }

View File

@@ -3,11 +3,6 @@
use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Response;
use function GuzzleHttp\Psr7\str; use function GuzzleHttp\Psr7\str;
function expose_view_path(string $path = '')
{
return base_path('resources/views/' . $path);
}
function respond_json($responseData, int $statusCode = 200) function respond_json($responseData, int $statusCode = 200)
{ {
return str(new Response( return str(new Response(

View File

@@ -91,8 +91,11 @@
<div class="max-w-screen-xl mx-auto py-3 px-3 sm:px-6 lg:px-8"> <div class="max-w-screen-xl mx-auto py-3 px-3 sm:px-6 lg:px-8">
<div class="pr-16 sm:text-center sm:px-16"> <div class="pr-16 sm:text-center sm:px-16">
<p class="font-medium text-white flex justify-center"> <p class="font-medium text-white flex justify-center">
<span class="inline-block">Waiting for requests on: <a class="underline" target="_blank" <span class="inline-block">Waiting for requests on:
href="%subdomains%">%subdomains%</a></span> {% for subdomain in subdomains %}
<a class="underline" target="_blank" href="http://{{ subdomain }}">{{ subdomain }}</a>
{% endfor %}
</span>
</p> </p>
</div> </div>
</div> </div>
@@ -135,14 +138,14 @@
@click="setLog(log)"> @click="setLog(log)">
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900"> <td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
<p> <p>
{{ log.request.method }} @{ log.request.method }
{{ log.request.uri }} @{ log.request.uri }
</p> </p>
<span class="text-xs">{{ log.subdomain }}</span> <span class="text-xs">@{ log.subdomain }</span>
</td> </td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500"> <td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response"> <div v-if="log.response">
{{ log.response.status }} - {{ log.response.reason }} @{ log.response.status } - @{ log.response.reason }
</div> </div>
<div v-else> <div v-else>
... ...
@@ -150,7 +153,7 @@
</td> </td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500"> <td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response"> <div v-if="log.response">
{{ log.duration }}ms @{ log.duration }ms
</div> </div>
</td> </td>
</tr> </tr>
@@ -163,7 +166,7 @@
<div v-if="currentLog" class="bg-white shadow overflow-hidden sm:rounded-lg"> <div v-if="currentLog" class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6"> <div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 flex"> <h3 class="text-lg leading-6 font-medium text-gray-900 flex">
{{ currentLog.request.method }} {{ currentLog.request.uri }} @{ currentLog.request.method } @{ currentLog.request.uri }
<div class="flex-grow"></div> <div class="flex-grow"></div>
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<button @click.prevent="replay(currentLog)" <button @click.prevent="replay(currentLog)"
@@ -182,7 +185,7 @@
</span> </span>
</h3> </h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500"> <p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
Status code: {{ currentLog.response?.status}} Status code: @{ currentLog.response?.status}
</p> </p>
</div> </div>
<div> <div>
@@ -223,10 +226,10 @@
:key="'query_' + name" :key="'query_' + name"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500"> <dt class="text-sm leading-5 font-medium text-gray-500">
{{ name }} @{ name }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ value }} @{ value }
</dd> </dd>
</div> </div>
@@ -239,12 +242,12 @@
:key="'post_' + name" :key="'post_' + name"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500"> <dt class="text-sm leading-5 font-medium text-gray-500">
{{ parameter.name }} @{ parameter.name }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<span <span
v-if="parameter.is_file">File: {{ parameter.filename }} ({{ parameter.mime_type }})</span> v-if="parameter.is_file">File: @{ parameter.filename } (@{ parameter.mime_type })</span>
<span v-else>{{ parameter.value }}</span> <span v-else>@{ parameter.value }</span>
</dd> </dd>
</div> </div>
@@ -257,15 +260,15 @@
:key="header" :key="header"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500"> <dt class="text-sm leading-5 font-medium text-gray-500">
{{ header }} @{ header }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ value }} @{ value }
</dd> </dd>
</div> </div>
<div> <div>
<pre class="p-6 prettyprint">{{ currentLog.request.body }}</pre> <pre class="p-6 prettyprint">@{ currentLog.request.body }</pre>
</div> </div>
</div> </div>
@@ -279,10 +282,10 @@
:key="header" :key="header"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500"> <dt class="text-sm leading-5 font-medium text-gray-500">
{{ header }} @{ header }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ value }} @{ value }
</dd> </dd>
</div> </div>
<div v-if="currentLog.request.additional_data.length !== 0"> <div v-if="currentLog.request.additional_data.length !== 0">
@@ -295,7 +298,7 @@
:key="'debug'+key" :key="'debug'+key"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500"> <dt class="text-sm leading-5 font-medium text-gray-500">
{{ key }} @{ key }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<div v-html="value"> <div v-html="value">
@@ -327,13 +330,18 @@
</div> </div>
</div> </div>
<div v-if="activeTab === 'raw'"> <div v-if="activeTab === 'raw'">
<pre class="p-6 text-sm">{{ currentLog.response.body }}</pre> <pre class="p-6 text-sm">@{ currentLog.response.body }</pre>
</div> </div>
<div v-if="activeTab === 'preview'"> <div v-if="activeTab === 'preview'">
<iframe :srcdoc="currentLog.response.body" style="height: 500px;" class="w-full h-full"></iframe> <iframe :srcdoc="currentLog.response.body" style="height: 500px;" class="w-full h-full"></iframe>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="flex-col bg-white shadow overflow-hidden sm:rounded-lg justify-center items-center flex py-4">
<h1 class="text-lg">Waiting for connections...</h1>
<img src="https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=http://{{ subdomains[0] | url_encode }}&choe=UTF-8" />
<a class="text-sm" href="http://{{ subdomains[0] }}" target="_blank">http://{{ subdomains[0] }}</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -342,6 +350,8 @@
new Vue({ new Vue({
el: '#app', el: '#app',
delimiters: ['@{', '}'],
data: { data: {
search: '', search: '',
currentLog: null, currentLog: null,
@@ -367,6 +377,7 @@
}, },
clearLogs: function() { clearLogs: function() {
fetch('/logs/clear'); fetch('/logs/clear');
this.currentLog = null;
}, },
setLog: function (log) { setLog: function (log) {
this.currentLog = log; this.currentLog = log;