Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Handling in HttpTransport when the requested uri does not exist #1431

Merged
merged 2 commits into from

3 participants

@elinw

The transports are currently not handling it well when the domain for a requested uri completely does not exist. At this point they are somewhat inconsistent in what they do (e.g. stream silences the message and then throws an exception similarly to what I propose here for socket). Rather than use a generic exception message this uses php and curl errors to give more detailed information about what went wrong.

@elinw

These came up when testing bad urls. The challenge was that fsockopen() sends a warning when the uri is not there ... the sending of the warning was making it very difficult to handle problem so this was the solution I finally came to after going around in circles for a while.

@piotr-cz

Some time ago I've send a PR #1022 that was addressing this issue across all transports ( fix for Curl has been merged yesterday in #1438).

I think it's important to keep same convention across all transport and throw an exception in the same place (probably request function).

@elinw

Well .. I agree consistent is good and as you see I also came to the conclusion that silencing was the only option for fsocketopen(). The no conent condition is different than the bad url condition. I also think it's worth considering that there may only be headers and no body (or body and no headers) to cover all scenarios.

@elinw elinw closed this
@elinw elinw reopened this
@elinw

There was a reason for doing it twice but I can't remember what it was (although I will test again with your version and see if it copes with all of my cases). I guess that it really doesn't matter that there are two different exceptions happening, one where the host is not foundand the other where it can't be opened for another reason.

@piotr-cz

@elinw
More descriptive exceptions are always welcome.
But there are some cases (authentication to external REST service) when there are multiple requests exchanged and every saved request counts.

Besides in my opinion in your previous code both assertions for fsockopen had same result and passing first would mean passing second as well.

Thanks for taking time at it.

@elinw

Yes I was trying to remember why I did that and I think I just was avoiding touching the existing code for no good reason and really more focused on the empty $content problem.

@elinw

Wow that was a not easy exercise in rebasing

libraries/joomla/http/transport/socket.php
((9 lines not shown))
// Split the response into headers and body.
$response = explode("\r\n\r\n", $content, 2);
// Get the response headers as an array.
$headers = explode("\r\n", $response[0]);
+ if (empty($response[1]))
+ {
+ throw new UnexpectedValueException('No [1] offset in response.');
@pasamio
pasamio added a note

If we got headers and an empty body, I'm not sure this is necessarily an exception.

@pasamio
pasamio added a note

Went and looked at the spec, a HEAD request by definition won't return a body:

The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.

So we really shouldn't return an exception here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pasamio pasamio merged commit e921add into joomla:staging
@elinw elinw deleted the elinw:socket branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
4 libraries/joomla/http/factory.php
@@ -19,7 +19,7 @@
class JHttpFactory
{
/**
- * method to recieve Http instance.
+ * Method to recieve Http instance.
*
* @param JRegistry $options Client options object.
* @param mixed $adapters Adapter (string) or queue of adapters (array) to use for communication.
@@ -58,7 +58,7 @@ public static function getAvailableDriver(JRegistry $options, $default = null)
settype($default, 'array');
$availableAdapters = $default;
}
- // Check if there is available http transport adapters
+ // Check if there is at least one available http transport adapter
if (!count($availableAdapters))
{
return false;
View
25 libraries/joomla/http/transport/curl.php
@@ -79,6 +79,7 @@ public function request($method, JUri $uri, $data = null, array $headers = null,
{
$options[CURLOPT_POSTFIELDS] = $data;
}
+
// Otherwise we need to encode the value first.
else
{
@@ -146,6 +147,20 @@ public function request($method, JUri $uri, $data = null, array $headers = null,
// Execute the request and close the connection.
$content = curl_exec($ch);
+ // Check if the content is a string. If it is not, it must be an error.
+ if (!is_string($content))
+ {
+ $message = curl_error($ch);
+
+ if (empty($message))
+ {
+ // Error but nothing from cURL? Create our own
+ $message = 'No HTTP response received';
+ }
+
+ throw new RuntimeException($message);
+ }
+
// Get the request information.
$info = curl_getinfo($ch);
@@ -158,7 +173,8 @@ public function request($method, JUri $uri, $data = null, array $headers = null,
/**
* Method to get a response object from a server response.
*
- * @param string $content The complete server response, including headers.
+ * @param string $content The complete server response, including headers
+ * as a string if the response has no errors.
* @param array $info The cURL request information.
*
* @return JHttpResponse
@@ -171,12 +187,6 @@ protected function getResponse($content, $info)
// Create the response object.
$return = new JHttpResponse;
- // Check if the content is actually a string.
- if (!is_string($content))
- {
- throw new UnexpectedValueException('No HTTP response received.');
- }
-
// Get the number of redirects that occurred.
$redirects = isset($info['redirect_count']) ? $info['redirect_count'] : 0;
@@ -202,6 +212,7 @@ protected function getResponse($content, $info)
{
$return->code = (int) $code;
}
+
// No valid response code was detected.
else
{
View
40 libraries/joomla/http/transport/socket.php
@@ -160,6 +160,11 @@ protected function getResponse($content)
// Create the response object.
$return = new JHttpResponse;
+ if (empty($content))
+ {
+ throw new UnexpectedValueException('No content in response.');
+ }
+
// Split the response into headers and body.
$response = explode("\r\n\r\n", $content, 2);
@@ -167,7 +172,7 @@ protected function getResponse($content)
$headers = explode("\r\n", $response[0]);
// Set the body for the response.
- $return->body = $response[1];
+ $return->body = empty($response[1]) ? '' : $response[1];
// Get the response code from the first offset of the response headers.
preg_match('/[0-9]{3}/', array_shift($headers), $matches);
@@ -177,6 +182,7 @@ protected function getResponse($content)
{
$return->code = (int) $code;
}
+
// No valid response code was detected.
else
{
@@ -217,6 +223,7 @@ protected function connect(JUri $uri, $timeout = null)
{
$port = ($uri->getScheme() == 'https') ? 443 : 80;
}
+
// Use the set port.
else
{
@@ -239,6 +246,7 @@ protected function connect(JUri $uri, $timeout = null)
throw new RuntimeException('Cannot close connection');
}
}
+
// Make sure the connection has not timed out.
elseif (!$meta['timed_out'])
{
@@ -248,15 +256,33 @@ protected function connect(JUri $uri, $timeout = null)
if (!is_numeric($timeout))
{
- $timeout = ini_get("default_socket_timeout");
+ $timeout = ini_get('default_socket_timeout');
}
- // Attempt to connect to the server.
- $connection = fsockopen($host, $port, $errno, $err, $timeout);
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ // PHP sends a warning if the uri does not exists; we silence it and throw an exception instead.
+ // Attempt to connect to the server
+ $connection = @fsockopen($host, $port, $errno, $err, $timeout);
if (!$connection)
{
- throw new RuntimeException($err, $errno);
+ if (!$php_errormsg)
+ {
+ // Error but nothing from php? Create our own
+ $php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
+ }
+
+ // Restore error tracking to give control to the exception handler
+ ini_set('track_errors', $track_errors);
+
+ throw new RuntimeException($php_errormsg);
}
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
// Since the connection was successful let's store it in case we need to use it later.
$this->connections[$key] = $connection;
@@ -271,9 +297,9 @@ protected function connect(JUri $uri, $timeout = null)
}
/**
- * method to check if http transport socket available for using
+ * Method to check if http transport socket available for use
*
- * @return bool true if available else false
+ * @return boolean True if available else false
*
* @since 12.1
*/
View
22 libraries/joomla/http/transport/stream.php
@@ -126,15 +126,30 @@ public function request($method, JUri $uri, $data = null, array $headers = null,
// Create the stream context for the request.
$context = stream_context_create(array('http' => $options));
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
// Open the stream for reading.
$stream = @fopen((string) $uri, 'r', false, $context);
- // Check if the stream is open.
if (!$stream)
{
- throw new RuntimeException(sprintf('Could not connect to resource: %s', $uri));
+ if (!$php_errormsg)
+ {
+ // Error but nothing from php? Create our own
+ $php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
+ }
+ // Restore error tracking to give control to the exception handler
+ ini_set('track_errors', $track_errors);
+
+ throw new RuntimeException($php_errormsg);
}
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
// Get the metadata for the stream, including response headers.
$metadata = stream_get_meta_data($stream);
@@ -174,6 +189,7 @@ protected function getResponse(array $headers, $body)
{
$return->code = (int) $code;
}
+
// No valid response code was detected.
else
{
@@ -191,7 +207,7 @@ protected function getResponse(array $headers, $body)
}
/**
- * method to check if http transport stream available for using
+ * Method to check if http transport stream available for use
*
* @return bool true if available else false
*
View
23 tests/suites/unit/joomla/http/JHttpTransportTest.php
@@ -100,6 +100,29 @@ public function testRequestGet($transportClass)
}
/**
+ * Tests the request method with a get request with a bad domain
+ *
+ * @dataProvider transportProvider
+ * @expectedException RuntimeException
+ */
+ public function testBadDomainRequestGet($transportClass)
+ {
+ $transport = new $transportClass($this->options);
+ $response = $transport->request('get', new JUri('http://xommunity.joomla.org'));
+ }
+
+ /**
+ * Tests the request method with a get request for non existant url
+ *
+ * @dataProvider transportProvider
+ */
+ public function testRequestGet404($transportClass)
+ {
+ $transport = new $transportClass($this->options);
+ $response = $transport->request('get', new JUri($this->stubUrl . ':80'));
+ }
+
+ /**
* Tests the request method with a put request
*
* @param string $transportClass @todo
Something went wrong with that request. Please try again.