diff --git c/core/includes/common.inc i/core/includes/common.inc
index e3f405f..d02bdd9 100644
--- c/core/includes/common.inc
+++ i/core/includes/common.inc
@@ -737,6 +737,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
  *   - data: A string containing the response body that was received.
  */
 function drupal_http_request($url, array $options = array()) {
+  global $db_prefix;
   $result = new stdClass();
 
   // Parse the URL and make sure we can handle the schema.
@@ -745,12 +746,14 @@ function drupal_http_request($url, array $options = array()) {
   if ($uri == FALSE) {
     $result->error = 'unable to parse URL';
     $result->code = -1001;
+    drupal_alter('http_response', $result, $uri, $options);
     return $result;
   }
 
   if (!isset($uri['scheme'])) {
     $result->error = 'missing schema';
     $result->code = -1002;
+    drupal_alter('http_response', $result, $uri, $options);
     return $result;
   }
 
@@ -771,6 +774,34 @@ function drupal_http_request($url, array $options = array()) {
     'User-Agent' => 'Drupal (+http://drupal.org/)',
   );
 
+  // If the server URL has a user then attempt to use basic authentication.
+  if (isset($uri['user'])) {
+    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
+  }
+
+  // If the database prefix is being used by SimpleTest to run the tests in a copied
+  // database then set the user-agent header to the database prefix so that any
+  // calls to other Drupal pages will run the SimpleTest prefixed database. The
+  // user-agent is used to ensure that multiple testing sessions running at the
+  // same time won't interfere with each other as they would if the database
+  // prefix were stored statically in a file or database variable.
+  if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
+    $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
+  }
+
+  // Allow modules to modify the request or even return a cached or generated
+  // response.
+  drupal_alter('http_request', $uri, $options, $result);
+
+  // Only add Content-Length if we actually have any content or if it is a POST
+  // or PUT request. Some non-standard servers get confused by Content-Length in
+  // at least HEAD/GET requests, and Squid always requires Content-Length in
+  // POST/PUT requests.
+  $content_length = strlen($options['data']);
+  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
+    $options['headers']['Content-Length'] = $content_length;
+  }
+
   // stream_socket_client() requires timeout to be a float.
   $options['timeout'] = (float) $options['timeout'];
 
@@ -830,6 +861,7 @@ function drupal_http_request($url, array $options = array()) {
     default:
       $result->error = 'invalid schema ' . $uri['scheme'];
       $result->code = -1003;
+      drupal_alter('http_response', $result, $uri, $options);
       return $result;
   }
 
@@ -847,6 +879,7 @@ function drupal_http_request($url, array $options = array()) {
     // clash with the HTTP status codes.
     $result->code = -$errno;
     $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
+    drupal_alter('http_response', $result, $uri, $options);
     return $result;
   }
 
@@ -919,6 +952,7 @@ function drupal_http_request($url, array $options = array()) {
   if ($info['timed_out']) {
     $result->code = HTTP_REQUEST_TIMEOUT;
     $result->error = 'request timed out';
+    drupal_alter('http_response', $result, $uri, $options);
     return $result;
   }
   // Parse response headers from the response body.
@@ -926,11 +960,11 @@ function drupal_http_request($url, array $options = array()) {
   // \n\n or \r\r instead of \r\n\r\n.
   list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
   $response = preg_split("/\r\n|\n|\r/", $response);
-
   // Parse the response status line.
-  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
-  $result->protocol = $protocol;
-  $result->status_message = $status_message;
+  $response_array = explode(' ', trim(array_shift($response)), 3);
+  $protocol = $result->protocol = $response_array[0];
+  $code = $response_array[1];
+  $status_message = $result->status_message = !empty($response_array[2]) ? $response_array[2] : NULL;
 
   $result->headers = array();
 
@@ -1024,6 +1058,7 @@ function drupal_http_request($url, array $options = array()) {
       $result->error = $status_message;
   }
 
+  drupal_alter('http_response', $result, $uri, $options);
   return $result;
 }
 
diff --git c/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php i/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php
index 276d82e..a9955ad 100644
--- c/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php
+++ i/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php
@@ -41,6 +41,16 @@ class HttpRequestTest extends WebTestBase {
     $this->assertEqual($unable_to_parse->code, -1001, t('Returned with "-1001" error code.'));
     $this->assertEqual($unable_to_parse->error, 'unable to parse URL', t('Returned with "unable to parse URL" error message.'));
 
+    // Test altering $url in hook_http_request_alter().
+    $result = drupal_http_request('http://changethisrequest.example.com/foo?bar=1');
+    $this->assertEqual($result->code, 200, t('Fetched page successfully.'));
+    $this->drupalSetContent($result->data);
+    $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('Request was altered successfully.'));
+
+    // Test altering $result in hook_http_request_alter().
+    $result = drupal_http_request('http://generated.example.com/foo?bar=1');
+    $this->assertEqual($result->data, 'This is a generated HTTP response.', t('Request was altered successfully.'));
+
     // Fetch page.
     $result = drupal_http_request(url('node', array('absolute' => TRUE)));
     $this->assertEqual($result->code, 200, t('Fetched page successfully.'));
diff --git c/core/modules/system/system.api.php i/core/modules/system/system.api.php
index 1333429..b1db8bd 100644
--- c/core/modules/system/system.api.php
+++ i/core/modules/system/system.api.php
@@ -3661,6 +3661,47 @@ function hook_system_themes_page_alter(&$theme_groups) {
 }
 
 /**
+ * Allows modules to alter HTTP requests made with drupal_http_request().
+ *
+ * This allows modules to adjust options, add HTTP headers, change the URL, or
+ * even return complete responses that are returned without sending the actual
+ * request.
+ *
+ * @param $uri
+ *   A URL array as returned by parse_url().
+ * @param $options
+ *   The options array passed to drupal_http_request().
+ * @param $result
+ *   The result object. By default, this is an empty class. If a module sets
+ *   $result->code, the networking code in drupal_http_request() is bypassed,
+ *   and the result is returned to the caller.
+ */
+function hook_http_request_alter(&$uri, &$options, &$result) {
+  // We talk a lot to this host, but it is unstable, so do not wait too long.
+  if ($uri['host'] == 'example.com') {
+    $options['timeout'] = 3;
+  }
+}
+
+/**
+ * Allows modules to act upon a completed HTTP request.
+ *
+ * @param $result
+ *   The result that is about to be returned from drupal_http_request(). Modules
+ *   may alter this.
+ * @param $uri
+ *   A URL array as returned by parse_url().
+ * @param $options
+ *   The options array passed to drupal_http_request().
+ */
+function hook_http_response(&$result, $uri, $options) {
+  // Our own XML-RPC server is unstable - log all timeouts.
+  if ($uri['host'] == 'xmlrpc.example.com' && $result->code == HTTP_REQUEST_TIMEOUT) {
+    watchdog('xmlrpc', 'Could not connect to xmlrpc.example.com.', array(), WATCHDOG_WARNING);
+  }
+}
+
+/**
  * Alters inbound URL requests.
  *
  * @param $path
diff --git c/core/modules/system/tests/modules/system_test/system_test.module i/core/modules/system/tests/modules/system_test/system_test.module
index 444d54d..1c55143 100644
--- c/core/modules/system/tests/modules/system_test/system_test.module
+++ i/core/modules/system/tests/modules/system_test/system_test.module
@@ -386,3 +386,32 @@ function system_test_authorize_init_page($page_title) {
   system_authorized_init('system_test_authorize_run', drupal_get_path('module', 'system_test') . '/system_test.module', array(), $page_title);
   drupal_goto($authorize_url);
 }
+
+/**
+ * Implements hook_http_request_alter().
+ */
+function system_test_http_request_alter(&$uri, &$options, &$result) {
+  global $base_url;
+  if ($uri['host'] == 'changethisrequest.example.com') {
+    // Modify the request URL.
+    $uri = parse_url(url('node', array('absolute' => TRUE)));
+  }
+  elseif ($uri['host'] == 'generated.example.com') {
+    // Generate a synthetic response.
+    $result->code = 200;
+    $result->protocol = 'HTTP/1.0';
+    $result->status_message = 'OK';
+    $result->headers = array('Content-Type' => 'text/plain');
+    $result->data = 'This is a generated HTTP response.';
+  }
+}
+
+/**
+ * Implements hook_http_response().
+ */
+function system_test_http_response(&$result, $uri, $options) {
+  if (!empty($uri['query']) && strpos($uri['query'], 'changethisresponse=1') !== FALSE) {
+    $result->data .= 'This HTTP response has been modified.';
+  }
+}
+
