1<?php 2namespace GuzzleHttp\Exception; 3 4use GuzzleHttp\Promise\PromiseInterface; 5use Psr\Http\Message\RequestInterface; 6use Psr\Http\Message\ResponseInterface; 7use Psr\Http\Message\UriInterface; 8 9/** 10 * HTTP Request exception 11 */ 12class RequestException extends TransferException 13{ 14 /** @var RequestInterface */ 15 private $request; 16 17 /** @var ResponseInterface|null */ 18 private $response; 19 20 /** @var array */ 21 private $handlerContext; 22 23 public function __construct( 24 $message, 25 RequestInterface $request, 26 ResponseInterface $response = null, 27 \Exception $previous = null, 28 array $handlerContext = [] 29 ) { 30 // Set the code of the exception if the response is set and not future. 31 $code = $response && !($response instanceof PromiseInterface) 32 ? $response->getStatusCode() 33 : 0; 34 parent::__construct($message, $code, $previous); 35 $this->request = $request; 36 $this->response = $response; 37 $this->handlerContext = $handlerContext; 38 } 39 40 /** 41 * Wrap non-RequestExceptions with a RequestException 42 * 43 * @param RequestInterface $request 44 * @param \Exception $e 45 * 46 * @return RequestException 47 */ 48 public static function wrapException(RequestInterface $request, \Exception $e) 49 { 50 return $e instanceof RequestException 51 ? $e 52 : new RequestException($e->getMessage(), $request, null, $e); 53 } 54 55 /** 56 * Factory method to create a new exception with a normalized error message 57 * 58 * @param RequestInterface $request Request 59 * @param ResponseInterface $response Response received 60 * @param \Exception $previous Previous exception 61 * @param array $ctx Optional handler context. 62 * 63 * @return self 64 */ 65 public static function create( 66 RequestInterface $request, 67 ResponseInterface $response = null, 68 \Exception $previous = null, 69 array $ctx = [] 70 ) { 71 if (!$response) { 72 return new self( 73 'Error completing request', 74 $request, 75 null, 76 $previous, 77 $ctx 78 ); 79 } 80 81 $level = (int) floor($response->getStatusCode() / 100); 82 if ($level === 4) { 83 $label = 'Client error'; 84 $className = ClientException::class; 85 } elseif ($level === 5) { 86 $label = 'Server error'; 87 $className = ServerException::class; 88 } else { 89 $label = 'Unsuccessful request'; 90 $className = __CLASS__; 91 } 92 93 $uri = $request->getUri(); 94 $uri = static::obfuscateUri($uri); 95 96 // Client Error: `GET /` resulted in a `404 Not Found` response: 97 // <html> ... (truncated) 98 $message = sprintf( 99 '%s: `%s %s` resulted in a `%s %s` response', 100 $label, 101 $request->getMethod(), 102 $uri, 103 $response->getStatusCode(), 104 $response->getReasonPhrase() 105 ); 106 107 $summary = static::getResponseBodySummary($response); 108 109 if ($summary !== null) { 110 $message .= ":\n{$summary}\n"; 111 } 112 113 return new $className($message, $request, $response, $previous, $ctx); 114 } 115 116 /** 117 * Get a short summary of the response 118 * 119 * Will return `null` if the response is not printable. 120 * 121 * @param ResponseInterface $response 122 * 123 * @return string|null 124 */ 125 public static function getResponseBodySummary(ResponseInterface $response) 126 { 127 return \GuzzleHttp\Psr7\get_message_body_summary($response); 128 } 129 130 /** 131 * Obfuscates URI if there is a username and a password present 132 * 133 * @param UriInterface $uri 134 * 135 * @return UriInterface 136 */ 137 private static function obfuscateUri(UriInterface $uri) 138 { 139 $userInfo = $uri->getUserInfo(); 140 141 if (false !== ($pos = strpos($userInfo, ':'))) { 142 return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); 143 } 144 145 return $uri; 146 } 147 148 /** 149 * Get the request that caused the exception 150 * 151 * @return RequestInterface 152 */ 153 public function getRequest() 154 { 155 return $this->request; 156 } 157 158 /** 159 * Get the associated response 160 * 161 * @return ResponseInterface|null 162 */ 163 public function getResponse() 164 { 165 return $this->response; 166 } 167 168 /** 169 * Check if a response was received 170 * 171 * @return bool 172 */ 173 public function hasResponse() 174 { 175 return $this->response !== null; 176 } 177 178 /** 179 * Get contextual information about the error from the underlying handler. 180 * 181 * The contents of this array will vary depending on which handler you are 182 * using. It may also be just an empty array. Relying on this data will 183 * couple you to a specific handler, but can give more debug information 184 * when needed. 185 * 186 * @return array 187 */ 188 public function getHandlerContext() 189 { 190 return $this->handlerContext; 191 } 192} 193