diff --git a/app/main/controller/api/map.php b/app/main/controller/api/map.php index 707be222..4d232336 100644 --- a/app/main/controller/api/map.php +++ b/app/main/controller/api/map.php @@ -655,7 +655,8 @@ class Map extends Controller\AccessController { // check if data for specific system is requested $systemData = (array)$f3->get('POST.systemData'); // update current location - $activeCharacter = $activeCharacter->updateLog(); + // -> suppress temporary timeout errors + $activeCharacter = $activeCharacter->updateLog(['suppressTimeoutErrors' => true]); // if data is requested extend the cache key in order to get new data $requestSystemData = (object) []; diff --git a/app/main/controller/ccp/sso.php b/app/main/controller/ccp/sso.php index 49e704b9..fc796261 100644 --- a/app/main/controller/ccp/sso.php +++ b/app/main/controller/ccp/sso.php @@ -174,6 +174,9 @@ class Sso extends Api\User{ $user->save(); } + /** + * @var $userCharactersModel Model\UserCharacterModel + */ if( is_null($userCharactersModel = $characterModel->userCharacter) ){ $userCharactersModel = Model\BasicModel::getNew('UserCharacterModel'); $userCharactersModel->characterId = $characterModel; @@ -377,24 +380,25 @@ class Sso extends Api\User{ /** * get all available Endpoints * @param $accessToken + * @param array $additionalOptions * @return mixed|null */ - protected function getEndpoints($accessToken){ + protected function getEndpoints($accessToken, $additionalOptions = []){ $crestUrl = self::getCrestEndpoint(); - $contentType = 'application/vnd.ccp.eve.Api-v3+json'; - $endpoint = $this->getEndpoint($accessToken, $crestUrl, $contentType); + $additionalOptions['contentType'] = 'application/vnd.ccp.eve.Api-v3+json'; + $endpoint = $this->getEndpoint($accessToken, $crestUrl, $additionalOptions); return $endpoint; } /** * get a specific endpoint by its $resourceUrl - * @param $accessToken - * @param $resourceUrl - * @param string $contentType + * @param string $accessToken CREST access token + * @param string $resourceUrl endpoint API url + * @param array $additionalOptions optional request options (pathfinder specific) * @return mixed|null */ - protected function getEndpoint($accessToken, $resourceUrl, $contentType = ''){ + protected function getEndpoint($accessToken, $resourceUrl, $additionalOptions = []){ $resourceUrlParts = parse_url($resourceUrl); $endpoint = null; @@ -412,13 +416,16 @@ class Sso extends Api\User{ // if specific contentType is required -> add it to request header // CREST versioning can be done by calling different "Accept:" Headers - if( !empty($contentType) ){ - $requestOptions['header'][] = 'Accept: ' . $contentType; + if( isset($additionalOptions['contentType']) ){ + $requestOptions['header'][] = 'Accept: ' . $additionalOptions['contentType']; } - $apiResponse = Lib\Web::instance()->request($resourceUrl, $requestOptions); + $apiResponse = Lib\Web::instance()->request($resourceUrl, $requestOptions, $additionalOptions); - if($apiResponse['headers']){ + if( + $apiResponse['timeout'] === false && + $apiResponse['headers'] + ){ // check headers for error $this->checkResponseHeaders($apiResponse['headers'], $requestOptions); @@ -441,9 +448,10 @@ class Sso extends Api\User{ * @param $accessToken * @param $endpoint * @param array $path - * @return null|string + * @param array $additionalOptions + * @return null */ - protected function walkEndpoint($accessToken, $endpoint, $path = []){ + protected function walkEndpoint($accessToken, $endpoint, $path = [], $additionalOptions = []){ $targetEndpoint = null; if( !empty($path) ){ @@ -451,8 +459,8 @@ class Sso extends Api\User{ if(isset($endpoint[$newNode])){ $currentEndpoint = $endpoint[$newNode]; if(isset($currentEndpoint['href'])){ - $newEndpoint = $this->getEndpoint($accessToken, $currentEndpoint['href']); - $targetEndpoint = $this->walkEndpoint($accessToken, $newEndpoint, $path); + $newEndpoint = $this->getEndpoint($accessToken, $currentEndpoint['href'], $additionalOptions); + $targetEndpoint = $this->walkEndpoint($accessToken, $newEndpoint, $path, $additionalOptions); }else{ // leaf found $targetEndpoint = $currentEndpoint; @@ -471,16 +479,17 @@ class Sso extends Api\User{ /** * get character data * @param $accessToken - * @return array + * @param array $additionalOptions + * @return object */ - protected function getCharacterData($accessToken){ - $endpoints = $this->getEndpoints($accessToken); + protected function getCharacterData($accessToken, $additionalOptions = []){ + $endpoints = $this->getEndpoints($accessToken, $additionalOptions); $characterData = (object) []; $endpoint = $this->walkEndpoint($accessToken, $endpoints, [ 'decode', 'character' - ]); + ], $additionalOptions); if( !empty($endpoint) ){ $characterData->character = (new Mapper\CrestCharacter($endpoint))->getData(); @@ -497,31 +506,45 @@ class Sso extends Api\User{ * -> solarSystem data where character is currently active * @param $accessToken * @param int $ttl - * @return array + * @param array $additionalOptions + * @return array|mixed */ - public function getCharacterLocationData($accessToken, $ttl = 10){ - $locationData = []; + public function getCharacterLocationData($accessToken, $ttl = 10, $additionalOptions = []){ + // null == CREST call failed (e.g. timeout) + $locationData = [ + 'timeout' => false + ]; // in addition to the cURL caching (based on cache-control headers, // the final location data is cached additionally -> speed up $cacheKey = sprintf(self::CACHE_KEY_LOCATION_DATA, 'TOKEN_' . hash('md5', $accessToken)); if( !$this->getF3()->exists($cacheKey) ){ - $endpoints = $this->getEndpoints($accessToken); + $endpoints = $this->getEndpoints($accessToken, $additionalOptions); $endpoint = $this->walkEndpoint($accessToken, $endpoints, [ 'decode', 'character', 'location' - ]); + ], $additionalOptions); + + if( !is_null($endpoint) ){ + // request succeeded (e.g. no timeout) - if( !empty($endpoint) ){ if(isset($endpoint['solarSystem'])){ $locationData['system'] = (new Mapper\CrestSystem($endpoint['solarSystem']))->getData(); } + + if(isset($endpoint['station'])){ + $locationData['station'] = (new Mapper\CrestStation($endpoint['station']))->getData(); + } + + $this->getF3()->set($cacheKey, $locationData, $ttl); + }else{ + // timeout + $locationData['timeout'] = true; } - $this->getF3()->set($cacheKey, $locationData, $ttl); }else{ $locationData = $this->getF3()->get($cacheKey); } diff --git a/app/main/data/mapper/creststation.php b/app/main/data/mapper/creststation.php new file mode 100644 index 00000000..c4e60ba5 --- /dev/null +++ b/app/main/data/mapper/creststation.php @@ -0,0 +1,18 @@ + 'id', + 'name' => 'name' + ]; +} \ No newline at end of file diff --git a/app/main/lib/web.php b/app/main/lib/web.php index 28ee6fd5..00a8d9ad 100644 --- a/app/main/lib/web.php +++ b/app/main/lib/web.php @@ -94,13 +94,15 @@ class Web extends \Web { * -> caches response by returned HTTP Cache header data * @param string $url * @param array|null $options + * @param array $additionalOptions * @return array|FALSE|mixed */ - public function request($url,array $options = null) { + public function request($url,array $options = null, $additionalOptions = []) { $f3 = \Base::instance(); if( !$f3->exists( $hash = $this->getCacheKey($url, $options) ) ){ $result = parent::request($url, $options); + $result['timeout'] = false; $statusCode = $this->getStatuscodeFromHeaders( $result['headers'] ); switch($statusCode){ @@ -119,7 +121,6 @@ class Web extends \Web { case 501: case 502: case 503: - case 504: case 505: $f3->error($statusCode, $this->getErrorMessageFromJsonResponse( $options['method'], @@ -127,8 +128,20 @@ class Web extends \Web { json_decode($result['body']) )); break; + case 504: case 0: - // timeout + // timeout -> response should not be cached + $result['timeout'] = true; + + if($additionalOptions['suppressTimeoutErrors'] !== true){ + // set error... + $f3->error(504, $this->getErrorMessageFromJsonResponse( + $options['method'], + $url + )); + } + + // log error LogController::getLogger('error')->write( sprintf(self::ERROR_TIMEOUT, $options['timeout'], $url) ); @@ -149,12 +162,12 @@ class Web extends \Web { /** * get error message from response object - * @param string $method - * @param string $url - * @param \stdClass $responseBody + * @param $method + * @param $url + * @param null|\stdClass $responseBody * @return string */ - protected function getErrorMessageFromJsonResponse($method, $url, $responseBody){ + protected function getErrorMessageFromJsonResponse($method, $url, $responseBody = null){ if( empty($responseBody->message) ){ $message = sprintf(self::ERROR_DEFAULT_MSG, $method, $url); }else{ diff --git a/app/main/model/charactermodel.php b/app/main/model/charactermodel.php index f69ae2a4..119dd172 100644 --- a/app/main/model/charactermodel.php +++ b/app/main/model/charactermodel.php @@ -334,10 +334,13 @@ class CharacterModel extends BasicModel { * update character log (active system, ...) * -> HTTP Header Data (if IGB) * -> CREST API request for character log data (if not IGB) - * @return CharacterModel + * @param array $additionalOptions (optional) request options for cURL request + * @return $this */ - public function updateLog(){ - + public function updateLog($additionalOptions = []){ + // check if everything is OK + // -> do not update log in case of temporary CREST timeouts + $updateLogData = false; $logData = []; $headerData = Controller\Controller::getIGBHeaderData(); @@ -354,33 +357,40 @@ class CharacterModel extends BasicModel { isset($formattedHeaderData['character']) && $formattedHeaderData['character']['id'] == $this->_id ){ + $updateLogData = true; $logData = $formattedHeaderData; } }else{ // get Location Data from CREST endpoint // user is NOT with IGB online OR has not jet set "trusted" page $ssoController = new Ccp\Sso(); - $logData = $ssoController->getCharacterLocationData($this->getAccessToken()); + $logData = $ssoController->getCharacterLocationData($this->getAccessToken(), 10, $additionalOptions); + + if($logData['timeout'] === false){ + $updateLogData = true; + } } - if( empty($logData) ){ - // character is not in-game - if(is_object($this->characterLog)){ - // delete existing log - $this->characterLog->erase(); - $this->save(); - } - }else{ - // character is currently in-game - if( !$characterLog = $this->getLog() ){ - // create new log - $characterLog = $this->rel('characterLog'); - $characterLog->characterId = $this->_id; - } - $characterLog->setData($logData); - $characterLog->save(); + if($updateLogData == true){ + if( empty($logData['system']) ){ + // character is not in-game + if(is_object($this->characterLog)){ + // delete existing log + $this->characterLog->erase(); + $this->save(); + } + }else{ + // character is currently in-game + if( !$characterLog = $this->getLog() ){ + // create new log + $characterLog = $this->rel('characterLog'); + $characterLog->characterId = $this->_id; + } + $characterLog->setData($logData); + $characterLog->save(); - $this->characterLog = $characterLog; + $this->characterLog = $characterLog; + } } return $this; diff --git a/app/main/model/systemsignaturemodel.php b/app/main/model/systemsignaturemodel.php index 258dea66..ee2ebafe 100644 --- a/app/main/model/systemsignaturemodel.php +++ b/app/main/model/systemsignaturemodel.php @@ -35,13 +35,13 @@ class SystemSignatureModel extends BasicModel { 'groupId' => [ 'type' => Schema::DT_INT, 'nullable' => false, - 'default' => 1, + 'default' => 0, 'index' => true, ], 'typeId' => [ 'type' => Schema::DT_INT, 'nullable' => false, - 'default' => 1, + 'default' => 0, 'index' => true, ], 'name' => [