From f2ac0be8308d561cf115e0162973f879f145bd11 Mon Sep 17 00:00:00 2001 From: Jairo Correa Date: Thu, 3 Mar 2022 19:36:09 -0300 Subject: [PATCH] Added host and extra options --- CHANGELOG-2.x.md | 8 +- README.md | 42 ++++++++++- src/NgrokCommand.php | 28 ++++--- src/NgrokProcessBuilder.php | 24 ++++-- src/NgrokServiceProvider.php | 2 +- tests/NgrokCommandTest.php | 90 ++++++++++++++++++++-- tests/NgrokProcessBuilderTest.php | 121 +++++++++++++++--------------- 7 files changed, 228 insertions(+), 87 deletions(-) diff --git a/CHANGELOG-2.x.md b/CHANGELOG-2.x.md index cb02c87..82c4c65 100644 --- a/CHANGELOG-2.x.md +++ b/CHANGELOG-2.x.md @@ -1,6 +1,12 @@ # Changelog -## [Unreleased](https://github.com/jn-jairo/laravel-ngrok/compare/v2.0.2...2.x) +## [Unreleased](https://github.com/jn-jairo/laravel-ngrok/compare/v2.0.3...2.x) + +## [v2.0.3 (2022-03-03)](https://github.com/jn-jairo/laravel-ngrok/compare/v2.0.2...v2.0.3) + +### Added +- `--host` and `--extra` options +- Allow ngrok url with region ## [v2.0.2 (2022-02-11)](https://github.com/jn-jairo/laravel-ngrok/compare/v2.0.1...v2.0.2) diff --git a/README.md b/README.md index 8bb2008..26e220b 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,48 @@ php artisan ngrok The parameters for ngrok will be extracted from your application. -You can also pass custom host and port. +## Advanced usage ```bash -php artisan ngrok example.com --port=8000 +php artisan ngrok [options] [--] [] +``` + + Argument | Description +:----------------|:------------------------------------------------------ + **host-header** | Host header to identify the app (Example: myapp.test) + + Option | Description +:------------------------|:----------------------------------------------------------- + **-H, --host[=HOST]** | Host to tunnel the requests (default: localhost) + **-P, --port[=PORT]** | Port to tunnel the requests (default: 80) + **-E, --extra[=EXTRA]** | Extra arguments to ngrok command (multiple values allowed) + + +## Examples + +```bash +# If you have multiples apps (myapp.test, my-other-app.test, ...) +# set it in the app.url configuration +# or pass it in the host-header argument + +php artisan ngrok myapp.test + +# If you use a different port, set it in the app.url configuration +# or pass it in the --port option + +php artisan ngrok --port=8000 myapp.test + +# If you use docker and have containers like (nginx, php, workspace) +# and wanna run the command inside the workspace container +# pass the name of the container the requests will tunnel through + +php artisan ngrok --host=nginx example.com + +# If you wanna pass other arguments directly to ngrok +# use the --extra or -E option + +php artisan ngrok --extra='--region=eu' -E'--config=ngrok.yml' + ``` ## License diff --git a/src/NgrokCommand.php b/src/NgrokCommand.php index 18d0f4e..127a4e2 100644 --- a/src/NgrokCommand.php +++ b/src/NgrokCommand.php @@ -15,8 +15,10 @@ class NgrokCommand extends Command * @var string */ protected $signature = 'ngrok - {host? : The host to share} - {--port= : The port to share}'; + {host-header? : Host header to identify the app (Example: myapp.test)} + {--H|host= : Host to tunnel the requests (default: localhost)} + {--P|port= : Port to tunnel the requests (default: 80)} + {--E|extra=* : Extra arguments to ngrok command}'; /** * The console command description. @@ -58,17 +60,19 @@ public function __construct(NgrokProcessBuilder $processBuilder, NgrokWebService */ public function handle() : int { - $host = $this->argument('host'); + $hostHeader = $this->argument('host-header'); + $host = $this->option('host'); $port = $this->option('port'); + $extra = $this->option('extra'); - if ($host === null) { + if ($hostHeader === null) { $url = $this->getLaravel()->make('config')->get('app.url'); $urlParsed = parse_url($url); if ($urlParsed !== false) { if (isset($urlParsed['host'])) { - $host = $urlParsed['host']; + $hostHeader = $urlParsed['host']; } if (isset($urlParsed['port']) && $port === null) { @@ -77,11 +81,12 @@ public function handle() : int } } - if (empty($host)) { - $this->error('Invalid host'); + if (empty($hostHeader)) { + $this->error('Invalid host header'); return 1; } + $host = $host ?: 'localhost'; $port = $port ?: '80'; $this->line('-----------------'); @@ -90,12 +95,17 @@ public function handle() : int $this->line(''); + $this->line('Host header: ' . $hostHeader); $this->line('Host: ' . $host); $this->line('Port: ' . $port); + if (! empty($extra)) { + $this->line('Extra: ' . implode(' ', $extra)); + } + $this->line(''); - $process = $this->processBuilder->buildProcess($host, $port); + $process = $this->processBuilder->buildProcess($hostHeader, $port, $host, $extra); return $this->runProcess($process); } @@ -129,7 +139,7 @@ private function runProcess(Process $process) : int if ($webServiceStarted && ! $tunnelStarted) { $tunnels = $webService->getTunnels(); - if (! empty($tunnels)) { + if (! empty($tunnels) && count($tunnels) > 1) { $tunnelStarted = true; foreach ($tunnels as $tunnel) { diff --git a/src/NgrokProcessBuilder.php b/src/NgrokProcessBuilder.php index f02d6a5..4a85a8c 100644 --- a/src/NgrokProcessBuilder.php +++ b/src/NgrokProcessBuilder.php @@ -44,20 +44,32 @@ public function getWorkingDirectory() : string /** * Build ngrok command. * - * @param string $host + * @param string $hostHeader * @param string $port + * @param string $host + * @param array $extra * @return \Symfony\Component\Process\Process */ - public function buildProcess(string $host = '', string $port = '80') : Process - { + public function buildProcess( + string $hostHeader = '', + string $port = '80', + string $host = '', + array $extra = [] + ) : Process { $command = ['ngrok', 'http', '--log', 'stdout']; - if ($host !== '') { + $command = array_merge($command, $extra); + + if ($hostHeader !== '') { $command[] = '--host-header'; - $command[] = $host; + $command[] = $hostHeader; } - $command[] = $port ?: '80'; + if ($host !== '') { + $command[] = $host . ':' . ($port ?: '80'); + } else { + $command[] = $port ?: '80'; + } return new Process($command, $this->getWorkingDirectory(), null, null, null); } diff --git a/src/NgrokServiceProvider.php b/src/NgrokServiceProvider.php index cbe80cf..6a62df4 100644 --- a/src/NgrokServiceProvider.php +++ b/src/NgrokServiceProvider.php @@ -112,6 +112,6 @@ private function extractOriginalHost(Request $request) : string */ private function isNgrokHost(string $host) : bool { - return preg_match('/^[-a-z0-9]+\.ngrok\.io$/i', $host); + return preg_match('/^[\.\-a-z0-9]+\.ngrok\.io$/i', $host); } } diff --git a/tests/NgrokCommandTest.php b/tests/NgrokCommandTest.php index fc57010..604e1b9 100644 --- a/tests/NgrokCommandTest.php +++ b/tests/NgrokCommandTest.php @@ -17,8 +17,10 @@ class NgrokCommandTest extends TestCase public function test_handle() : void { - $host = 'example.com'; + $hostHeader = 'example.com'; $port = '80'; + $host = 'localhost'; + $extra = []; config(['app.url' => '']); @@ -56,21 +58,90 @@ public function test_handle() : void $process->getExitCode()->willReturn(0)->shouldBeCalled(); $processBuilder = $this->prophesize(NgrokProcessBuilder::class); - $processBuilder->buildProcess($host, $port)->willReturn($process->reveal())->shouldBeCalled(); + $processBuilder->buildProcess($hostHeader, $port, $host, $extra)->willReturn($process->reveal())->shouldBeCalled(); app()->instance(NgrokWebService::class, $webService->reveal()); app()->instance(NgrokProcessBuilder::class, $processBuilder->reveal()); - $this->artisan('ngrok', ['host' => $host, '--port' => $port]) + $this->artisan('ngrok', [ + 'host-header' => $hostHeader, + '--host' => $host, + '--port' => $port, + ]) + ->expectsOutput('Host header: ' . $hostHeader) ->expectsOutput('Host: ' . $host) ->expectsOutput('Port: ' . $port) ->assertExitCode(0); } + public function test_handle_extra() : void + { + $hostHeader = 'example.com'; + $port = '80'; + $host = 'nginx'; + $extra = ['--region=eu', '--config=ngrok.yml']; + $extraString = '--region=eu --config=ngrok.yml'; + + config(['app.url' => '']); + + $tunnels = [ + [ + 'public_url' => 'http://0000-0000.ngrok.io', + 'config' => ['addr' => 'nginx:80'], + ], + [ + 'public_url' => 'https://0000-0000.ngrok.io', + 'config' => ['addr' => 'nginx:80'], + ], + ]; + + $webService = $this->prophesize(NgrokWebService::class); + $webService->setUrl('http://127.0.0.1:4040')->shouldBeCalled(); + $webService->getTunnels()->willReturn($tunnels)->shouldBeCalled(); + + $process = $this->prophesize(Process::class); + $process->run(\Prophecy\Argument::type('callable'))->will(function ($args) use ($process) { + $callback = $args[0]; + + $process->getOutput()->willReturn('msg="starting web service" addr=127.0.0.1:4040')->shouldBeCalled(); + $process->clearOutput()->willReturn($process)->shouldBeCalled(); + + $callback(Process::OUT, 'msg="starting web service" addr=127.0.0.1:4040'); + + $process->clearErrorOutput()->willReturn($process)->shouldBeCalled(); + + $callback(Process::ERR, 'error'); + + return 0; + })->shouldBeCalled(); + $process->getErrorOutput()->willReturn('')->shouldBeCalled(); + $process->getExitCode()->willReturn(0)->shouldBeCalled(); + + $processBuilder = $this->prophesize(NgrokProcessBuilder::class); + $processBuilder->buildProcess($hostHeader, $port, $host, $extra)->willReturn($process->reveal())->shouldBeCalled(); + + app()->instance(NgrokWebService::class, $webService->reveal()); + app()->instance(NgrokProcessBuilder::class, $processBuilder->reveal()); + + $this->artisan('ngrok', [ + 'host-header' => $hostHeader, + '--host' => $host, + '--port' => $port, + '--extra' => $extra, + ]) + ->expectsOutput('Host header: ' . $hostHeader) + ->expectsOutput('Host: ' . $host) + ->expectsOutput('Port: ' . $port) + ->expectsOutput('Extra: ' . $extraString) + ->assertExitCode(0); + } + public function test_handle_from_config() : void { - $host = 'example.com'; + $hostHeader = 'example.com'; $port = '8000'; + $host = 'localhost'; + $extra = []; config(['app.url' => 'http://example.com:8000']); @@ -108,21 +179,24 @@ public function test_handle_from_config() : void $process->getExitCode()->willReturn(0)->shouldBeCalled(); $processBuilder = $this->prophesize(NgrokProcessBuilder::class); - $processBuilder->buildProcess($host, $port)->willReturn($process->reveal())->shouldBeCalled(); + $processBuilder->buildProcess($hostHeader, $port, $host, $extra)->willReturn($process->reveal())->shouldBeCalled(); app()->instance(NgrokWebService::class, $webService->reveal()); app()->instance(NgrokProcessBuilder::class, $processBuilder->reveal()); $this->artisan('ngrok') + ->expectsOutput('Host header: ' . $hostHeader) ->expectsOutput('Host: ' . $host) ->expectsOutput('Port: ' . $port) ->assertExitCode(0); } - public function test_handle_invalid_host() : void + public function test_handle_invalid_host_header() : void { - $host = 'example.com'; + $hostHeader = 'example.com'; $port = '8000'; + $host = 'localhost'; + $extra = []; config(['app.url' => '']); @@ -147,7 +221,7 @@ public function test_handle_invalid_host() : void $process->getExitCode()->willReturn(0)->shouldNotBeCalled(); $processBuilder = $this->prophesize(NgrokProcessBuilder::class); - $processBuilder->buildProcess($host, $port)->willReturn($process->reveal())->shouldNotBeCalled(); + $processBuilder->buildProcess($hostHeader, $port, $host, $extra)->willReturn($process->reveal())->shouldNotBeCalled(); app()->instance(NgrokWebService::class, $webService->reveal()); app()->instance(NgrokProcessBuilder::class, $processBuilder->reveal()); diff --git a/tests/NgrokProcessBuilderTest.php b/tests/NgrokProcessBuilderTest.php index 49e7c72..46fa047 100644 --- a/tests/NgrokProcessBuilderTest.php +++ b/tests/NgrokProcessBuilderTest.php @@ -11,74 +11,75 @@ */ class NgrokProcessBuilderTest extends TestCase { - public function test_build_process_default() : void + public function buildProcessProvider() : array { - $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess(); - - $this->assertInstanceOf(Process::class, $process); - - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', $process->getCommandLine()); - $this->assertSame(__DIR__, $process->getWorkingDirectory()); - $this->assertNull($process->getTimeout()); - } - - public function test_build_process_host() : void - { - $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess('example.com'); - - $this->assertInstanceOf(Process::class, $process); - - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'80\'', $process->getCommandLine()); - $this->assertSame(__DIR__, $process->getWorkingDirectory()); - $this->assertNull($process->getTimeout()); - } - - public function test_build_process_host_port() : void - { - $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess('example.com', '8000'); - - $this->assertInstanceOf(Process::class, $process); - - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'8000\'', $process->getCommandLine()); - $this->assertSame(__DIR__, $process->getWorkingDirectory()); - $this->assertNull($process->getTimeout()); - } - - public function test_build_process_host_empty_port() : void - { - $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess('example.com', ''); - - $this->assertInstanceOf(Process::class, $process); - - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'80\'', $process->getCommandLine()); - $this->assertSame(__DIR__, $process->getWorkingDirectory()); - $this->assertNull($process->getTimeout()); - } - - public function test_build_process_empty_host() : void - { - $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess(''); - - $this->assertInstanceOf(Process::class, $process); - - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', $process->getCommandLine()); - $this->assertSame(__DIR__, $process->getWorkingDirectory()); - $this->assertNull($process->getTimeout()); + return [ + 'default' => [ + [], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', + ], + 'host_header' => [ + ['example.com'], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'80\'', + ], + 'host_header_port' => [ + ['example.com', '8000'], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'8000\'', + ], + 'host_header_port_host' => [ + ['example.com', '8000', 'nginx'], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'nginx:8000\'', + ], + 'host_header_empty_port' => [ + ['example.com', ''], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'80\'', + ], + 'host_header_empty_port_empty_host' => [ + ['example.com', '', ''], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'80\'', + ], + 'host_header_empty_port_host' => [ + ['example.com', '', 'nginx'], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--host-header\' \'example.com\' \'nginx:80\'', + ], + 'empty_host_header_emtpy_port_host' => [ + ['', '', 'nginx'], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'nginx:80\'', + ], + 'empty_host_header' => [ + [''], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', + ], + 'empty_host_header_emtpy_port' => [ + ['', ''], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', + ], + 'empty_host_header_emtpy_port_empty_host' => [ + ['', '', ''], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', + ], + 'extra_single' => [ + ['example.com', '', '', ['--region=eu']], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--region=eu\' \'--host-header\' \'example.com\' \'80\'', + ], + 'extra_multiple' => [ + ['example.com', '', '', ['--region=eu', '--config=../ngrok.yml']], + '\'ngrok\' \'http\' \'--log\' \'stdout\' \'--region=eu\' \'--config=../ngrok.yml\' \'--host-header\' \'example.com\' \'80\'', + ], + ]; } - public function test_build_process_empty_host_emtpy_port() : void + /** + * @dataProvider buildProcessProvider + */ + public function test_build_process(array $args, string $command) : void { $processBuilder = new NgrokProcessBuilder(__DIR__); - $process = $processBuilder->buildProcess('', ''); + $process = $processBuilder->buildProcess(...$args); $this->assertInstanceOf(Process::class, $process); - $this->assertSame('\'ngrok\' \'http\' \'--log\' \'stdout\' \'80\'', $process->getCommandLine()); + $this->assertSame($command, $process->getCommandLine()); $this->assertSame(__DIR__, $process->getWorkingDirectory()); $this->assertNull($process->getTimeout()); }