From b0d5296c04614e34dbdce51b781483498972cd77 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 20:33:21 -0500 Subject: [PATCH 1/7] remove "magic math" --- public/index.php | 8 ++++---- src/Server.php | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 src/Server.php diff --git a/public/index.php b/public/index.php index 8a43411f..ebd4bfd6 100644 --- a/public/index.php +++ b/public/index.php @@ -3,7 +3,9 @@ ini_set('display_errors', '0'); ini_set('display_startup_errors', '0'); -require __DIR__ . '/../vendor/autoload.php'; +$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); + +require $projectRoot . '/vendor/autoload.php'; /** * Rergardless of what happens next, we'll need a basline markown converter. @@ -15,9 +17,7 @@ ->smartPunctuation(); // Inject environment variables to global $_SERVER array -Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); - -$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +Dotenv\Dotenv::createImmutable($projectRoot)->load(); /** * Verifying setup is valid. diff --git a/src/Server.php b/src/Server.php new file mode 100644 index 00000000..eb43b33f --- /dev/null +++ b/src/Server.php @@ -0,0 +1,10 @@ + Date: Wed, 27 Oct 2021 20:59:51 -0500 Subject: [PATCH 2/7] Server class reintroduced --- public/index.php | 31 ++-------------------------- src/Server.php | 43 +++++++++++++++++++++++++++++++++++++++ tests/Pest.php | 2 ++ tests/ServerTest.php | 42 ++++++++++++++++++++++++++++++++++++++ tests/Server_.php | 48 -------------------------------------------- 5 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 tests/ServerTest.php delete mode 100644 tests/Server_.php diff --git a/public/index.php b/public/index.php index ebd4bfd6..96edd79e 100644 --- a/public/index.php +++ b/public/index.php @@ -19,29 +19,8 @@ // Inject environment variables to global $_SERVER array Dotenv\Dotenv::createImmutable($projectRoot)->load(); -/** - * Verifying setup is valid. - */ -$requestRequiredServerGlobals = [ - 'APP_ENV', - 'CONTENT_UP', - 'CONTENT_FOLDER', - 'REQUEST_SCHEME', - 'HTTP_HOST', - 'REQUEST_URI' -]; - -// TESTING -// unset($_SERVER['APP_ENV']); -$requestHasRequiredServerGlobals = true; -foreach ($requestRequiredServerGlobals as $key) { - if (! array_key_exists($key, $_SERVER)) { - $requestHasRequiredServerGlobals = false; - break; - } -} - -if (! $requestHasRequiredServerGlobals) { +$server = JoshBruce\Site\Server::init($_SERVER); +if ($server->isMissingRequiredValues()) { $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') ->for('/500.md'); @@ -62,12 +41,6 @@ exit; } -if ($_SERVER['APP_ENV'] !== 'production') { - $erroHandler = new Whoops\Run; - $erroHandler->pushHandler(new Whoops\Handler\PrettyPageHandler); - $erroHandler->register(); -} - /** * Verifying specified content area exists. */ diff --git a/src/Server.php b/src/Server.php index eb43b33f..c25cc362 100644 --- a/src/Server.php +++ b/src/Server.php @@ -4,7 +4,50 @@ namespace JoshBruce\Site; +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; + class Server { + /** + * @param array $serverGlobals + */ + public static function init(array $serverGlobals): Server + { + return new Server(serverGlobals: $serverGlobals); + } + + /** + * @param array $serverGlobals + */ + public function __construct(private array $serverGlobals) + { + } + + public function isMissingRequiredValues(): bool + { + $required = [ + 'APP_ENV', + 'CONTENT_UP', + 'CONTENT_FOLDER', + 'REQUEST_SCHEME', + 'HTTP_HOST', + 'REQUEST_URI' + ]; + + $hasRequired = true; + foreach ($required as $key) { + if (! array_key_exists($key, $this->serverGlobals)) { + return true; + } + } + + if ($this->serverGlobals['APP_ENV'] !== 'production') { + $erroHandler = new Run(); + $erroHandler->pushHandler(new PrettyPageHandler()); + $erroHandler->register(); + } + return false; + } } diff --git a/tests/Pest.php b/tests/Pest.php index de4dd446..48e59de3 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -57,6 +57,8 @@ function serverGlobals(string $requestUri = '/'): array $_SERVER['APP_ENV'] = 'test'; $_SERVER['CONTENT_UP'] = 0; $_SERVER['CONTENT_FOLDER'] = '/tests/test-content'; + $_SERVER['REQUEST_SCHEME'] = 'http'; + $_SERVER['HTTP_HOST'] = 'testing.com'; $_SERVER['REQUEST_URI'] = $requestUri; $_SERVER['REQUEST_METHOD'] = 'get'; diff --git a/tests/ServerTest.php b/tests/ServerTest.php new file mode 100644 index 00000000..a088e784 --- /dev/null +++ b/tests/ServerTest.php @@ -0,0 +1,42 @@ +isMissingRequiredValues() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + unset($serverGlobals['CONTENT_UP']); + + expect( + Server::init($serverGlobals)->isMissingRequiredValues() + )->toBeTrue(); +})->group('server'); + +// it('is immutable', function() { +// $server = Server::init(serverGlobals()); + +// $server->offsetSet('CONTENT_UP', 2); +// expect( +// $server->offsetGet('CONTENT_UP') +// )->toBe( +// 0 +// ); + +// $server->offsetUnset('CONTENT_UP'); +// expect( +// $server->offsetGet('CONTENT_UP') +// )->toBe( +// 0 +// ); +// })->group('server'); + +// test('Server exists', function() { +// expect( +// Server::init(serverGlobals()) +// )->toBeInstanceOf( +// Server::class +// ); +// })->group('server'); diff --git a/tests/Server_.php b/tests/Server_.php deleted file mode 100644 index 5c622f90..00000000 --- a/tests/Server_.php +++ /dev/null @@ -1,48 +0,0 @@ -response()->isOk() - )->toBeTrue(); - - $serverGlobals = serverGlobals(); - unset($serverGlobals['CONTENT_UP']); - unset($serverGlobals['CONTENT_FOLDER']); - - $response = Server::init($serverGlobals)->response(); - expect( - $response->isOk() - )->toBeFalse(); - - expect( - $response->getBody() - )->toBeString()->not->toBeEmpty(); -})->group('server'); - -it('is immutable', function() { - $server = Server::init(serverGlobals()); - - $server->offsetSet('CONTENT_UP', 2); - expect( - $server->offsetGet('CONTENT_UP') - )->toBe( - 0 - ); - - $server->offsetUnset('CONTENT_UP'); - expect( - $server->offsetGet('CONTENT_UP') - )->toBe( - 0 - ); -})->group('server'); - -test('Server exists', function() { - expect( - Server::init(serverGlobals()) - )->toBeInstanceOf( - Server::class - ); -})->group('server'); From d4253de1b8c48f220b88b58b9fbd9d41be1ecdd7 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 21:09:10 -0500 Subject: [PATCH 3/7] content fork up date - no tests testing the logic related to this method would be "testing the compiler" so to speak. Server only retrieves keys from an array and content only negates the results of standard library methods. --- public/index.php | 8 +++++--- src/Content.php | 9 +++++++-- src/Server.php | 10 ++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/public/index.php b/public/index.php index 96edd79e..88e5dd88 100644 --- a/public/index.php +++ b/public/index.php @@ -20,6 +20,7 @@ Dotenv\Dotenv::createImmutable($projectRoot)->load(); $server = JoshBruce\Site\Server::init($_SERVER); + if ($server->isMissingRequiredValues()) { $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') ->for('/500.md'); @@ -49,11 +50,11 @@ // $_SERVER['CONTENT_FOLDER'] = '/does/not/exist'; $content = JoshBruce\Site\Content::init( $projectRoot, - $_SERVER['CONTENT_UP'], - $_SERVER['CONTENT_FOLDER'] + $server->contentUp(), + $server->contentFolder() ); -if (! $content->folderDoesExist()) { +if ($content->folderIsMissing()) { $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') ->for('/502.md'); @@ -97,6 +98,7 @@ $requestIsForFile = strpos($requestUri, '.') > 0; $localFilePath = $requestUri . '/content.md'; + if ($requestIsForFile) { $folderMap = [ '/css' => '/.assets/styles', diff --git a/src/Content.php b/src/Content.php index 6e3ed209..842faee6 100644 --- a/src/Content.php +++ b/src/Content.php @@ -40,9 +40,9 @@ public function __construct( ) { } - public function folderDoesExist(): bool + public function folderIsMissing(): bool { - return file_exists($this->root()) and is_dir($this->root()); + return ! $this->folderExists(); } public function for(string $path): Content @@ -131,6 +131,11 @@ public function redirectPath(): string return ''; } + private function folderExists(): bool + { + return file_exists($this->root()) and is_dir($this->root()); + } + private function exists(): bool { return file_exists($this->filePath()); diff --git a/src/Server.php b/src/Server.php index c25cc362..b0a456bc 100644 --- a/src/Server.php +++ b/src/Server.php @@ -50,4 +50,14 @@ public function isMissingRequiredValues(): bool return false; } + + public function contentUp(): int + { + return intval($this->serverGlobals['CONTENT_UP']); + } + + public function contentFolder(): string + { + return strval($this->serverGlobals['CONTENT_FOLDER']); + } } From 784e0952fd7c9e018c18a49678a81d3a271ee35b Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 21:09:57 -0500 Subject: [PATCH 4/7] Update ContentTest.php --- tests/ContentTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ContentTest.php b/tests/ContentTest.php index 685f70b3..df2bf33e 100644 --- a/tests/ContentTest.php +++ b/tests/ContentTest.php @@ -35,5 +35,5 @@ 'text/html' ); - expect($this->baseContent->folderDoesExist())->toBeTrue(); + expect($this->baseContent->folderIsMissing())->toBeFalse(); })->group('content'); From 511d99598145c1931f748f49f16f94a7e944b706 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Thu, 28 Oct 2021 06:51:01 -0500 Subject: [PATCH 5/7] added 405 response The site only allows GET requests. --- public/index.php | 33 ++++++++++++++++++++++++----- setup-errors/405.md | 12 +++++++++++ {500-errors => setup-errors}/500.md | 0 {500-errors => setup-errors}/502.md | 0 src/Server.php | 14 ++++++++++++ tests/ServerTest.php | 13 ++++++++++++ 6 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 setup-errors/405.md rename {500-errors => setup-errors}/500.md (100%) rename {500-errors => setup-errors}/502.md (100%) diff --git a/public/index.php b/public/index.php index 88e5dd88..9e20c722 100644 --- a/public/index.php +++ b/public/index.php @@ -22,7 +22,7 @@ $server = JoshBruce\Site\Server::init($_SERVER); if ($server->isMissingRequiredValues()) { - $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') ->for('/500.md'); JoshBruce\Site\Emitter::emitWithResponse( @@ -43,11 +43,34 @@ } /** - * Verifying specified content area exists. + * Verifying the method is supported by the app */ +if ($server->isUsingUnsupportedMethod()) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') + ->for('/405.md'); -// TESTING -// $_SERVER['CONTENT_FOLDER'] = '/does/not/exist'; + JoshBruce\Site\Emitter::emitWithResponse( + 405, + [ + 'Cache-Control' => [ + 'no-cache', + 'must-revalidate' + ], + 'Allow' => $server->supportedMethods() + ], + Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + $markdownConverter->convert($content->markdown()) + )->build() + ); + exit; +} + + +/** + * Verifying specified content area exists. + */ $content = JoshBruce\Site\Content::init( $projectRoot, $server->contentUp(), @@ -55,7 +78,7 @@ ); if ($content->folderIsMissing()) { - $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') ->for('/502.md'); JoshBruce\Site\Emitter::emitWithResponse( diff --git a/setup-errors/405.md b/setup-errors/405.md new file mode 100644 index 00000000..94b07c38 --- /dev/null +++ b/setup-errors/405.md @@ -0,0 +1,12 @@ +--- +title: Unsupported method +usage: The request is using an Unsupported method. +--- + +# 405: Unsupported method + +We cannot support that type of request. + +Please try again later. + +If this error persists, please contact [Josh Bruce](https://github.com/joshbruce). diff --git a/500-errors/500.md b/setup-errors/500.md similarity index 100% rename from 500-errors/500.md rename to setup-errors/500.md diff --git a/500-errors/502.md b/setup-errors/502.md similarity index 100% rename from 500-errors/502.md rename to setup-errors/502.md diff --git a/src/Server.php b/src/Server.php index b0a456bc..c7ebe4c3 100644 --- a/src/Server.php +++ b/src/Server.php @@ -51,6 +51,20 @@ public function isMissingRequiredValues(): bool return false; } + public function isUsingUnsupportedMethod(): bool + { + $requestMethod = $this->serverGlobals['REQUEST_METHOD']; + return ! in_array($requestMethod, $this->supportedMethods()); + } + + /** + * @return string[] [description] + */ + public function supportedMethods(): array + { + return ['GET']; + } + public function contentUp(): int { return intval($this->serverGlobals['CONTENT_UP']); diff --git a/tests/ServerTest.php b/tests/ServerTest.php index a088e784..16b75e97 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -2,6 +2,19 @@ use JoshBruce\Site\Server; +it('limits request methods', function() { + expect( + Server::init(serverGlobals())->isUsingUnsupportedMethod() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_METHOD'] = 'INVALID'; + + expect( + Server::init($serverGlobals)->isUsingUnsupportedMethod() + )->toBeTrue(); +})->group('server'); + it('has required variables', function() { expect( Server::init(serverGlobals())->isMissingRequiredValues() From 34ab10dd174632af2a458ff13c5d3f427b2176de Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Thu, 28 Oct 2021 07:11:06 -0500 Subject: [PATCH 6/7] local file path creation added to Server --- public/index.php | 60 ++------------------------------------------ src/Server.php | 44 +++++++++++++++++++++++++++++++- tests/ServerTest.php | 30 ++++++++++++++++++++++ 3 files changed, 75 insertions(+), 59 deletions(-) diff --git a/public/index.php b/public/index.php index 9e20c722..7704232a 100644 --- a/public/index.php +++ b/public/index.php @@ -42,9 +42,6 @@ exit; } -/** - * Verifying the method is supported by the app - */ if ($server->isUsingUnsupportedMethod()) { $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') ->for('/405.md'); @@ -67,10 +64,6 @@ exit; } - -/** - * Verifying specified content area exists. - */ $content = JoshBruce\Site\Content::init( $projectRoot, $server->contentUp(), @@ -98,51 +91,7 @@ exit; } -/** - * Bootsrap is complete: local response time 19ms - * - * Does the requested content exist? - */ -$requestUri = $_SERVER['REQUEST_URI']; -if ($requestUri === '/') { - $requestUri = ''; -} - -// TESTING -// $requestUri = '/does/not/exist'; // 404 -// -// $requestUri = '/assets/favicons/favicon-16x16.png'; // file -// -// Check browser address becomes /design-your-life -// if ($requestUri !== '/design-your-life') { // redirecting -// $requestUri = '/self-improvement'; // redirecting -// } // redirecting - -$requestIsForFile = strpos($requestUri, '.') > 0; - -$localFilePath = $requestUri . '/content.md'; - -if ($requestIsForFile) { - $folderMap = [ - '/css' => '/.assets/styles', - '/js' => '/.assets/scripts', - '/assets' => '/.assets' - ]; - - $parts = explode('/', $requestUri); - $parts = array_filter($parts); - $first = array_shift($parts); - - $folderMapKey = '/' . $first; - - if (array_key_exists($folderMapKey, $folderMap)) { - $replace = $folderMap[$folderMapKey]; - - $localFilePath = str_replace($folderMapKey, $replace, $requestUri); - } -} - -$content = $content->for($localFilePath); +$content = $content->for($server->filePathForRequest()); if ($content->notFound()) { $content = $content->for(path: '/.errors/404.md'); JoshBruce\Site\Emitter::emitWithResponse( @@ -162,12 +111,7 @@ exit; } -/** - * Target file exists: local response time 27ms - * - * Handle file - */ -if ($requestIsForFile) { +if ($server->isRequestingFile()) { JoshBruce\Site\Emitter::emitWithResponseFile( 200, [ diff --git a/src/Server.php b/src/Server.php index c7ebe4c3..0908fef5 100644 --- a/src/Server.php +++ b/src/Server.php @@ -53,10 +53,15 @@ public function isMissingRequiredValues(): bool public function isUsingUnsupportedMethod(): bool { - $requestMethod = $this->serverGlobals['REQUEST_METHOD']; + $requestMethod = strtoupper($this->serverGlobals['REQUEST_METHOD']); return ! in_array($requestMethod, $this->supportedMethods()); } + public function isRequestingFile(): bool + { + return strpos($this->requestUri(), '.') > 0; + } + /** * @return string[] [description] */ @@ -74,4 +79,41 @@ public function contentFolder(): string { return strval($this->serverGlobals['CONTENT_FOLDER']); } + + public function filePathForRequest(): string + { + if ($this->isRequestingFile()) { + $folderMap = [ + '/css' => '/.assets/styles', + '/js' => '/.assets/scripts', + '/assets' => '/.assets' + ]; + + $parts = explode('/', $this->requestUri()); + $parts = array_filter($parts); + $first = array_shift($parts); + + $folderMapKey = '/' . $first; + + if (array_key_exists($folderMapKey, $folderMap)) { + $replace = $folderMap[$folderMapKey]; + + return str_replace( + $folderMapKey, + $replace, + $this->requestUri() + ); + } + return $this->requestUri(); + } + return $this->requestUri() . '/content.md'; + } + + private function requestUri(): string + { + if ($this->serverGlobals['REQUEST_URI'] === '/') { + return ''; + } + return $this->serverGlobals['REQUEST_URI']; + } } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 16b75e97..66f41015 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -2,6 +2,36 @@ use JoshBruce\Site\Server; +it('expected local file path', function() { + expect( + Server::init(serverGlobals())->filePathForRequest() + )->toBe( + '/content.md' + ); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_URI'] = '/some.file'; + + expect( + Server::init($serverGlobals)->filePathForRequest() + )->toBe( + '/some.file' + ); +})->group('server'); + +it('can determine if request is for a file', function() { + expect( + Server::init(serverGlobals())->isRequestingFile() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_URI'] = '/some.file'; + + expect( + Server::init($serverGlobals)->isRequestingFile() + )->toBeTrue(); +})->group('server'); + it('limits request methods', function() { expect( Server::init(serverGlobals())->isUsingUnsupportedMethod() From e224d00991275264ccd0bd581ba308bcd261a047 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Thu, 28 Oct 2021 07:20:05 -0500 Subject: [PATCH 7/7] redirection manual test put back Should be the only manual test we have now. Not sure how to automate. --- public/index.php | 15 +++++++++------ src/Content.php | 5 +++++ src/Server.php | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/public/index.php b/public/index.php index 7704232a..f11fdb5d 100644 --- a/public/index.php +++ b/public/index.php @@ -91,6 +91,13 @@ exit; } +// TESTING: Redirection +// Check browser address becomes /design-your-life +// if ($server->requestUri() !== '/design-your-life') { +// $_SERVER['REQUEST_URI'] = '/self-improvement'; +// $server = JoshBruce\Site\Server::init($_SERVER); +// } + $content = $content->for($server->filePathForRequest()); if ($content->notFound()) { $content = $content->for(path: '/.errors/404.md'); @@ -123,18 +130,14 @@ exit; } -/** - * Target file wants to redirect us: local response time 40ms - */ -$redirectPath = $content->redirectPath(); -if (strlen($redirectPath) > 0) { +if ($content->hasMoved()) { $scheme = $_SERVER['REQUEST_SCHEME']; $serverName = $_SERVER['HTTP_HOST']; $requestDomain = $scheme . '://' . $serverName; JoshBruce\Site\Emitter::emitWithResponse( 301, [ - 'Location' => $requestDomain . $redirectPath + 'Location' => $requestDomain . $content->redirectPath() ] ); exit; diff --git a/src/Content.php b/src/Content.php index 842faee6..9a6386cf 100644 --- a/src/Content.php +++ b/src/Content.php @@ -61,6 +61,11 @@ public function notFound(): bool return ! $this->exists(); } + public function hasMoved(): bool + { + return strlen($this->redirectPath()) > 0; + } + public function filePath(): string { return $this->root() . $this->path; diff --git a/src/Server.php b/src/Server.php index 0908fef5..dac4e345 100644 --- a/src/Server.php +++ b/src/Server.php @@ -109,7 +109,7 @@ public function filePathForRequest(): string return $this->requestUri() . '/content.md'; } - private function requestUri(): string + public function requestUri(): string { if ($this->serverGlobals['REQUEST_URI'] === '/') { return '';