mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 08:12:40 +01:00
Improve top-level exception handling
Summary: Fixes T6692. Addresses two main issues: - The write guard would sometimes not get disposed of on exception pathways, generating an unnecessary secondary error which was just a symptom of the original root error. - This was generally confusing and reduced the quality of reports we received because users would report the symptomatic error sometimes instead of the real error. - Instead, reflow the handling so that we always dispose of the write guard if we create one. - If we missed the Controller-level error page generation (normally, a nice page with full CSS, etc), we'd jump straight to Startup-level error page generation (very basic plain text). - A large class of errors occur too early or too late to be handled by Controller-level pages, but many of these errors are not fundamental, and the plain text page is excessively severe. - Provide a mid-level simple HTML error page for errors which can't get full CSS, but also aren't so fundamental that we have no recourse but plain text. Test Plan: Mid-level errors now produce an intentional-looking error page: {F259885} Verified that setup errors still render properly. @chad, feel free to tweak the exception page -- I just did a rough pass on it. Like the setup error stuff, it doesn't have Celerity, so we can't use `{$colors}` and no other CSS will be loaded. Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley, chad Maniphest Tasks: T6692 Differential Revision: https://secure.phabricator.com/D11126
This commit is contained in:
parent
2660b944be
commit
08126d3904
10 changed files with 411 additions and 214 deletions
|
@ -45,6 +45,7 @@ return array(
|
|||
'rsrc/css/application/config/config-template.css' => '25d446d6',
|
||||
'rsrc/css/application/config/config-welcome.css' => 'b0d16200',
|
||||
'rsrc/css/application/config/setup-issue.css' => '8f852bc0',
|
||||
'rsrc/css/application/config/unhandled-exception.css' => '38f08073',
|
||||
'rsrc/css/application/conpherence/menu.css' => 'e1e0fdf1',
|
||||
'rsrc/css/application/conpherence/message-pane.css' => '042886d1',
|
||||
'rsrc/css/application/conpherence/notification.css' => '04a6e10a',
|
||||
|
@ -816,6 +817,7 @@ return array(
|
|||
'sprite-tokens-css' => '1706b943',
|
||||
'syntax-highlighting-css' => '56c1ba38',
|
||||
'tokens-css' => '3d0f239e',
|
||||
'unhandled-exception-css' => '38f08073',
|
||||
),
|
||||
'requires' => array(
|
||||
'00861799' => array(
|
||||
|
|
|
@ -161,12 +161,14 @@ phutil_register_library_map(array(
|
|||
'AphrontResponse' => 'aphront/response/AphrontResponse.php',
|
||||
'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php',
|
||||
'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php',
|
||||
'AphrontStandaloneHTMLResponse' => 'aphront/response/AphrontStandaloneHTMLResponse.php',
|
||||
'AphrontTableView' => 'view/control/AphrontTableView.php',
|
||||
'AphrontTagView' => 'view/AphrontTagView.php',
|
||||
'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php',
|
||||
'AphrontTwoColumnView' => 'view/layout/AphrontTwoColumnView.php',
|
||||
'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php',
|
||||
'AphrontURIMapper' => 'aphront/AphrontURIMapper.php',
|
||||
'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php',
|
||||
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
|
||||
'AphrontView' => 'view/AphrontView.php',
|
||||
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
|
||||
|
@ -3217,11 +3219,13 @@ phutil_register_library_map(array(
|
|||
'AphrontRequestTestCase' => 'PhabricatorTestCase',
|
||||
'AphrontSideNavFilterView' => 'AphrontView',
|
||||
'AphrontStackTraceView' => 'AphrontView',
|
||||
'AphrontStandaloneHTMLResponse' => 'AphrontHTMLResponse',
|
||||
'AphrontTableView' => 'AphrontView',
|
||||
'AphrontTagView' => 'AphrontView',
|
||||
'AphrontTokenizerTemplateView' => 'AphrontView',
|
||||
'AphrontTwoColumnView' => 'AphrontView',
|
||||
'AphrontTypeaheadTemplateView' => 'AphrontView',
|
||||
'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse',
|
||||
'AphrontUsageException' => 'AphrontException',
|
||||
'AphrontView' => array(
|
||||
'Phobject',
|
||||
|
@ -4669,7 +4673,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
|
||||
'PhabricatorConfigResponse' => 'AphrontHTMLResponse',
|
||||
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
|
||||
'PhabricatorConfigSchemaQuery' => 'Phobject',
|
||||
'PhabricatorConfigSchemaSpec' => 'Phobject',
|
||||
'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema',
|
||||
|
|
|
@ -15,18 +15,20 @@ final class PhabricatorCelerityTestCase extends PhabricatorTestCase {
|
|||
$new_map = id(new CelerityResourceMapGenerator($resources))
|
||||
->generate();
|
||||
|
||||
$this->assertEqual(
|
||||
$new_map->getNameMap(),
|
||||
$old_map->getNameMap());
|
||||
$this->assertEqual(
|
||||
$new_map->getSymbolMap(),
|
||||
$old_map->getSymbolMap());
|
||||
$this->assertEqual(
|
||||
$new_map->getRequiresMap(),
|
||||
$old_map->getRequiresMap());
|
||||
$this->assertEqual(
|
||||
$new_map->getPackageMap(),
|
||||
$old_map->getPackageMap());
|
||||
// Don't actually compare these values with assertEqual(), since the diff
|
||||
// isn't helpful and is often enormously huge.
|
||||
|
||||
$maps_are_identical =
|
||||
($new_map->getNameMap() === $old_map->getNameMap()) &&
|
||||
($new_map->getSymbolMap() === $old_map->getSymbolMap()) &&
|
||||
($new_map->getRequiresMap() === $old_map->getRequiresMap()) &&
|
||||
($new_map->getPackageMap() === $old_map->getPackageMap());
|
||||
|
||||
$this->assertTrue(
|
||||
$maps_are_identical,
|
||||
pht(
|
||||
'When this test fails, it means the Celerity resource map is out '.
|
||||
'of date. Run `bin/celerity map` to rebuild it.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,192 @@ abstract class AphrontApplicationConfiguration {
|
|||
public function willBuildRequest() {}
|
||||
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PhabricatorStartup
|
||||
*/
|
||||
public static function runHTTPRequest(AphrontHTTPSink $sink) {
|
||||
PhabricatorEnv::initializeWebEnvironment();
|
||||
|
||||
$debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
|
||||
if ($debug_time_limit) {
|
||||
PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
|
||||
}
|
||||
|
||||
// This is the earliest we can get away with this, we need env config first.
|
||||
PhabricatorAccessLog::init();
|
||||
$access_log = PhabricatorAccessLog::getLog();
|
||||
PhabricatorStartup::setGlobal('log.access', $access_log);
|
||||
$access_log->setData(
|
||||
array(
|
||||
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
|
||||
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
|
||||
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
|
||||
));
|
||||
|
||||
DarkConsoleXHProfPluginAPI::hookProfiler();
|
||||
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
|
||||
|
||||
$response = PhabricatorSetupCheck::willProcessRequest();
|
||||
if ($response) {
|
||||
PhabricatorStartup::endOutputCapture();
|
||||
$sink->writeResponse($response);
|
||||
return;
|
||||
}
|
||||
|
||||
$host = AphrontRequest::getHTTPHeader('Host');
|
||||
$path = $_REQUEST['__path__'];
|
||||
|
||||
switch ($host) {
|
||||
default:
|
||||
$config_key = 'aphront.default-application-configuration-class';
|
||||
$application = PhabricatorEnv::newObjectFromConfig($config_key);
|
||||
break;
|
||||
}
|
||||
|
||||
$application->setHost($host);
|
||||
$application->setPath($path);
|
||||
$application->willBuildRequest();
|
||||
$request = $application->buildRequest();
|
||||
|
||||
// Build the server URI implied by the request headers. If an administrator
|
||||
// has not configured "phabricator.base-uri" yet, we'll use this to generate
|
||||
// links.
|
||||
|
||||
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
|
||||
$request_base_uri = "{$request_protocol}://{$host}/";
|
||||
PhabricatorEnv::setRequestBaseURI($request_base_uri);
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'U' => (string)$request->getRequestURI()->getPath(),
|
||||
));
|
||||
|
||||
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
|
||||
|
||||
$processing_exception = null;
|
||||
try {
|
||||
$response = $application->processRequest($request, $access_log, $sink);
|
||||
$response_code = $response->getHTTPResponseCode();
|
||||
} catch (Exception $ex) {
|
||||
$processing_exception = $ex;
|
||||
$response_code = 500;
|
||||
}
|
||||
|
||||
$write_guard->dispose();
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'c' => $response_code,
|
||||
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
|
||||
));
|
||||
|
||||
$access_log->write();
|
||||
|
||||
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
|
||||
|
||||
// Add points to the rate limits for this request.
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$user_ip = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
// The base score for a request allows users to make 30 requests per
|
||||
// minute.
|
||||
$score = (1000 / 30);
|
||||
|
||||
// If the user was logged in, let them make more requests.
|
||||
if ($request->getUser() && $request->getUser()->getPHID()) {
|
||||
$score = $score / 5;
|
||||
}
|
||||
|
||||
PhabricatorStartup::addRateLimitScore($user_ip, $score);
|
||||
}
|
||||
|
||||
if ($processing_exception) {
|
||||
throw $processing_exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function processRequest(
|
||||
AphrontRequest $request,
|
||||
PhutilDeferredLog $access_log,
|
||||
AphrontHTTPSink $sink) {
|
||||
|
||||
$this->setRequest($request);
|
||||
|
||||
list($controller, $uri_data) = $this->buildController();
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'C' => get_class($controller),
|
||||
));
|
||||
|
||||
$request->setURIMap($uri_data);
|
||||
$controller->setRequest($request);
|
||||
|
||||
// If execution throws an exception and then trying to render that
|
||||
// exception throws another exception, we want to show the original
|
||||
// exception, as it is likely the root cause of the rendering exception.
|
||||
$original_exception = null;
|
||||
try {
|
||||
$response = $controller->willBeginExecution();
|
||||
|
||||
if ($request->getUser() && $request->getUser()->getPHID()) {
|
||||
$access_log->setData(
|
||||
array(
|
||||
'u' => $request->getUser()->getUserName(),
|
||||
'P' => $request->getUser()->getPHID(),
|
||||
));
|
||||
}
|
||||
|
||||
if (!$response) {
|
||||
$controller->willProcessRequest($uri_data);
|
||||
$response = $controller->handleRequest($request);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$original_exception = $ex;
|
||||
$response = $this->handleException($ex);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $controller->didProcessRequest($response);
|
||||
$response = $this->willSendResponse($response, $controller);
|
||||
$response->setRequest($request);
|
||||
|
||||
$unexpected_output = PhabricatorStartup::endOutputCapture();
|
||||
if ($unexpected_output) {
|
||||
$unexpected_output = pht(
|
||||
"Unexpected output:\n\n%s",
|
||||
$unexpected_output);
|
||||
|
||||
phlog($unexpected_output);
|
||||
|
||||
if ($response instanceof AphrontWebpageResponse) {
|
||||
echo phutil_tag(
|
||||
'div',
|
||||
array('style' =>
|
||||
'background: #eeddff;'.
|
||||
'white-space: pre-wrap;'.
|
||||
'z-index: 200000;'.
|
||||
'position: relative;'.
|
||||
'padding: 8px;'.
|
||||
'font-family: monospace',
|
||||
),
|
||||
$unexpected_output);
|
||||
}
|
||||
}
|
||||
|
||||
$sink->writeResponse($response);
|
||||
} catch (Exception $ex) {
|
||||
if ($original_exception) {
|
||||
throw $original_exception;
|
||||
}
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/* -( URI Routing )-------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
63
src/aphront/response/AphrontStandaloneHTMLResponse.php
Normal file
63
src/aphront/response/AphrontStandaloneHTMLResponse.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
abstract class AphrontStandaloneHTMLResponse
|
||||
extends AphrontHTMLResponse {
|
||||
|
||||
abstract protected function getResources();
|
||||
abstract protected function getResponseTitle();
|
||||
abstract protected function getResponseBodyClass();
|
||||
abstract protected function getResponseBody();
|
||||
abstract protected function buildPlainTextResponseString();
|
||||
|
||||
final public function buildResponseString() {
|
||||
// Check to make sure we aren't requesting this via Ajax or Conduit.
|
||||
if (isset($_REQUEST['__ajax__']) || isset($_REQUEST['__conduit__'])) {
|
||||
return (string)hsprintf('%s', $this->buildPlainTextResponseString());
|
||||
}
|
||||
|
||||
$title = $this->getResponseTitle();
|
||||
$resources = $this->buildResources();
|
||||
$body_class = $this->getResponseBodyClass();
|
||||
$body = $this->getResponseBody();
|
||||
|
||||
return (string)hsprintf(
|
||||
<<<EOTEMPLATE
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>%s</title>
|
||||
%s
|
||||
</head>
|
||||
%s
|
||||
</html>
|
||||
EOTEMPLATE
|
||||
,
|
||||
$title,
|
||||
$resources,
|
||||
phutil_tag(
|
||||
'body',
|
||||
array(
|
||||
'class' => $body_class,
|
||||
),
|
||||
$body));
|
||||
}
|
||||
|
||||
private function buildResources() {
|
||||
$paths = $this->getResources();
|
||||
|
||||
$webroot = dirname(phutil_get_library_root('phabricator')).'/webroot/';
|
||||
|
||||
$resources = array();
|
||||
foreach ($paths as $path) {
|
||||
$resources[] = phutil_tag(
|
||||
'style',
|
||||
array('type' => 'text/css'),
|
||||
phutil_safe_html(Filesystem::readFile($webroot.'/rsrc/'.$path)));
|
||||
}
|
||||
|
||||
return phutil_implode_html("\n", $resources);
|
||||
}
|
||||
|
||||
|
||||
}
|
74
src/aphront/response/AphrontUnhandledExceptionResponse.php
Normal file
74
src/aphront/response/AphrontUnhandledExceptionResponse.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
final class AphrontUnhandledExceptionResponse
|
||||
extends AphrontStandaloneHTMLResponse {
|
||||
|
||||
private $exception;
|
||||
|
||||
public function setException(Exception $exception) {
|
||||
$this->exception = $exception;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHTTPResponseCode() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
protected function getResources() {
|
||||
return array(
|
||||
'css/application/config/config-template.css',
|
||||
'css/application/config/unhandled-exception.css',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getResponseTitle() {
|
||||
return pht('Unhandled Exception');
|
||||
}
|
||||
|
||||
protected function getResponseBodyClass() {
|
||||
return 'unhandled-exception';
|
||||
}
|
||||
|
||||
protected function getResponseBody() {
|
||||
$ex = $this->exception;
|
||||
|
||||
if ($ex instanceof AphrontUsageException) {
|
||||
$title = $ex->getTitle();
|
||||
} else {
|
||||
$title = get_class($ex);
|
||||
}
|
||||
|
||||
$body = $ex->getMessage();
|
||||
$body = phutil_escape_html_newlines($body);
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'unhandled-exception-detail',
|
||||
),
|
||||
array(
|
||||
phutil_tag(
|
||||
'h1',
|
||||
array(
|
||||
'class' => 'unhandled-exception-title',
|
||||
),
|
||||
$title),
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'unhandled-exception-body',
|
||||
),
|
||||
$body),
|
||||
));
|
||||
}
|
||||
|
||||
protected function buildPlainTextResponseString() {
|
||||
$ex = $this->exception;
|
||||
|
||||
return pht(
|
||||
'%s: %s',
|
||||
get_class($ex),
|
||||
$ex->getMessage());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorConfigResponse extends AphrontHTMLResponse {
|
||||
final class PhabricatorConfigResponse extends AphrontStandaloneHTMLResponse {
|
||||
|
||||
private $view;
|
||||
|
||||
|
@ -9,51 +9,33 @@ final class PhabricatorConfigResponse extends AphrontHTMLResponse {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function buildResponseString() {
|
||||
// Check to make sure we aren't requesting this via ajax or conduit
|
||||
if (isset($_REQUEST['__ajax__']) || isset($_REQUEST['__conduit__'])) {
|
||||
// We don't want to flood the console with html, just return a simple
|
||||
// message for now.
|
||||
return pht(
|
||||
'This install has a fatal setup error, access the internet web '.
|
||||
'version to view details and resolve it.');
|
||||
}
|
||||
|
||||
$resources = $this->buildResources();
|
||||
|
||||
$view = $this->view->render();
|
||||
|
||||
return hsprintf(
|
||||
'<!DOCTYPE html>'.
|
||||
'<html>'.
|
||||
'<head>'.
|
||||
'<meta charset="UTF-8" />'.
|
||||
'<title>Phabricator Setup</title>'.
|
||||
'%s'.
|
||||
'</head>'.
|
||||
'<body class="setup-fatal">%s</body>'.
|
||||
'</html>',
|
||||
$resources,
|
||||
$view);
|
||||
public function getHTTPResponseCode() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
private function buildResources() {
|
||||
$css = array(
|
||||
'application/config/config-template.css',
|
||||
'application/config/setup-issue.css',
|
||||
protected function getResources() {
|
||||
return array(
|
||||
'css/application/config/config-template.css',
|
||||
'css/application/config/setup-issue.css',
|
||||
);
|
||||
|
||||
$webroot = dirname(phutil_get_library_root('phabricator')).'/webroot/';
|
||||
|
||||
$resources = array();
|
||||
foreach ($css as $path) {
|
||||
$resources[] = phutil_tag(
|
||||
'style',
|
||||
array('type' => 'text/css'),
|
||||
phutil_safe_html(Filesystem::readFile($webroot.'/rsrc/css/'.$path)));
|
||||
}
|
||||
return phutil_implode_html("\n", $resources);
|
||||
}
|
||||
|
||||
protected function getResponseTitle() {
|
||||
return pht('Phabricator Setup Error');
|
||||
}
|
||||
|
||||
protected function getResponseBodyClass() {
|
||||
return 'setup-fatal';
|
||||
}
|
||||
|
||||
protected function getResponseBody() {
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
protected function buildPlainTextResponseString() {
|
||||
return pht(
|
||||
'This install has a fatal setup error, access the internet web '.
|
||||
'version to view details and resolve it.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -122,6 +122,10 @@ final class PhabricatorStartup {
|
|||
self::setupPHP();
|
||||
self::verifyPHP();
|
||||
|
||||
// If we've made it this far, the environment isn't completely broken so
|
||||
// we can switch over to relying on our own exception recovery mechanisms.
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
self::rateLimitRequest($_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
|
|
|
@ -11,174 +11,27 @@ if (file_exists($preamble_path)) {
|
|||
|
||||
PhabricatorStartup::didStartup();
|
||||
|
||||
$show_unexpected_traces = false;
|
||||
try {
|
||||
PhabricatorStartup::loadCoreLibraries();
|
||||
|
||||
PhabricatorEnv::initializeWebEnvironment();
|
||||
|
||||
$debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
|
||||
if ($debug_time_limit) {
|
||||
PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
|
||||
}
|
||||
|
||||
$show_unexpected_traces = PhabricatorEnv::getEnvConfig(
|
||||
'phabricator.developer-mode');
|
||||
|
||||
// This is the earliest we can get away with this, we need env config first.
|
||||
PhabricatorAccessLog::init();
|
||||
$access_log = PhabricatorAccessLog::getLog();
|
||||
PhabricatorStartup::setGlobal('log.access', $access_log);
|
||||
$access_log->setData(
|
||||
array(
|
||||
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
|
||||
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
|
||||
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
|
||||
));
|
||||
|
||||
DarkConsoleXHProfPluginAPI::hookProfiler();
|
||||
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
|
||||
|
||||
$sink = new AphrontPHPHTTPSink();
|
||||
|
||||
$response = PhabricatorSetupCheck::willProcessRequest();
|
||||
if ($response) {
|
||||
PhabricatorStartup::endOutputCapture();
|
||||
$sink->writeResponse($response);
|
||||
return;
|
||||
}
|
||||
|
||||
$host = AphrontRequest::getHTTPHeader('Host');
|
||||
$path = $_REQUEST['__path__'];
|
||||
|
||||
switch ($host) {
|
||||
default:
|
||||
$config_key = 'aphront.default-application-configuration-class';
|
||||
$application = PhabricatorEnv::newObjectFromConfig($config_key);
|
||||
break;
|
||||
}
|
||||
|
||||
$application->setHost($host);
|
||||
$application->setPath($path);
|
||||
$application->willBuildRequest();
|
||||
$request = $application->buildRequest();
|
||||
|
||||
// Until an administrator sets "phabricator.base-uri", assume it is the same
|
||||
// as the request URI. This will work fine in most cases, it just breaks down
|
||||
// when daemons need to do things.
|
||||
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
|
||||
$request_base_uri = "{$request_protocol}://{$host}/";
|
||||
PhabricatorEnv::setRequestBaseURI($request_base_uri);
|
||||
|
||||
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
|
||||
|
||||
$application->setRequest($request);
|
||||
list($controller, $uri_data) = $application->buildController();
|
||||
$request->setURIMap($uri_data);
|
||||
$controller->setRequest($request);
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'U' => (string)$request->getRequestURI()->getPath(),
|
||||
'C' => get_class($controller),
|
||||
));
|
||||
|
||||
// If execution throws an exception and then trying to render that exception
|
||||
// throws another exception, we want to show the original exception, as it is
|
||||
// likely the root cause of the rendering exception.
|
||||
$original_exception = null;
|
||||
try {
|
||||
$response = $controller->willBeginExecution();
|
||||
|
||||
if ($request->getUser() && $request->getUser()->getPHID()) {
|
||||
$access_log->setData(
|
||||
array(
|
||||
'u' => $request->getUser()->getUserName(),
|
||||
'P' => $request->getUser()->getPHID(),
|
||||
));
|
||||
}
|
||||
|
||||
if (!$response) {
|
||||
$controller->willProcessRequest($uri_data);
|
||||
$response = $controller->handleRequest($request);
|
||||
}
|
||||
AphrontApplicationConfiguration::runHTTPRequest($sink);
|
||||
} catch (Exception $ex) {
|
||||
$original_exception = $ex;
|
||||
$response = $application->handleException($ex);
|
||||
}
|
||||
try {
|
||||
$response = new AphrontUnhandledExceptionResponse();
|
||||
$response->setException($ex);
|
||||
|
||||
try {
|
||||
$response = $controller->didProcessRequest($response);
|
||||
$response = $application->willSendResponse($response, $controller);
|
||||
$response->setRequest($request);
|
||||
|
||||
$unexpected_output = PhabricatorStartup::endOutputCapture();
|
||||
if ($unexpected_output) {
|
||||
$unexpected_output = "Unexpected output:\n\n{$unexpected_output}";
|
||||
phlog($unexpected_output);
|
||||
|
||||
if ($response instanceof AphrontWebpageResponse) {
|
||||
echo phutil_tag(
|
||||
'div',
|
||||
array('style' =>
|
||||
'background: #eeddff;'.
|
||||
'white-space: pre-wrap;'.
|
||||
'z-index: 200000;'.
|
||||
'position: relative;'.
|
||||
'padding: 8px;'.
|
||||
'font-family: monospace',
|
||||
),
|
||||
$unexpected_output);
|
||||
}
|
||||
PhabricatorStartup::endOutputCapture();
|
||||
$sink->writeResponse($response);
|
||||
} catch (Exception $response_exception) {
|
||||
// If we hit a rendering exception, ignore it and throw the original
|
||||
// exception. It is generally more interesting and more likely to be
|
||||
// the root cause.
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$sink->writeResponse($response);
|
||||
} catch (Exception $ex) {
|
||||
$write_guard->dispose();
|
||||
$access_log->write();
|
||||
if ($original_exception) {
|
||||
$ex = new PhutilAggregateException(
|
||||
'Multiple exceptions during processing and rendering.',
|
||||
array(
|
||||
$original_exception,
|
||||
$ex,
|
||||
));
|
||||
}
|
||||
PhabricatorStartup::didEncounterFatalException(
|
||||
'Rendering Exception',
|
||||
$ex,
|
||||
$show_unexpected_traces);
|
||||
}
|
||||
|
||||
$write_guard->dispose();
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'c' => $response->getHTTPResponseCode(),
|
||||
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
|
||||
));
|
||||
|
||||
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
|
||||
|
||||
// Add points to the rate limits for this request.
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$user_ip = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
// The base score for a request allows users to make 30 requests per
|
||||
// minute.
|
||||
$score = (1000 / 30);
|
||||
|
||||
// If the user was logged in, let them make more requests.
|
||||
if ($request->getUser() && $request->getUser()->getPHID()) {
|
||||
$score = $score / 5;
|
||||
}
|
||||
|
||||
PhabricatorStartup::addRateLimitScore($user_ip, $score);
|
||||
}
|
||||
|
||||
} catch (Exception $ex) {
|
||||
PhabricatorStartup::didEncounterFatalException(
|
||||
'Core Exception',
|
||||
$ex,
|
||||
$show_unexpected_traces);
|
||||
PhabricatorStartup::didEncounterFatalException('Core Exception', $ex, false);
|
||||
}
|
||||
|
|
27
webroot/rsrc/css/application/config/unhandled-exception.css
Normal file
27
webroot/rsrc/css/application/config/unhandled-exception.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* @provides unhandled-exception-css
|
||||
*/
|
||||
|
||||
.unhandled-exception {
|
||||
background: #222228;
|
||||
}
|
||||
|
||||
.unhandled-exception-detail {
|
||||
max-width: 760px;
|
||||
margin: 16px auto;
|
||||
background: #f7f7f7;
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.unhandled-exception-detail .unhandled-exception-title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background: #DFE0E2;
|
||||
}
|
||||
|
||||
.unhandled-exception-detail .unhandled-exception-body {
|
||||
padding: 16px;
|
||||
color: #4B4D51;
|
||||
}
|
Loading…
Reference in a new issue