diff options
author | Thomas Steur <tsteur@users.noreply.github.com> | 2019-09-17 23:20:26 +0300 |
---|---|---|
committer | diosmosis <diosmosis@users.noreply.github.com> | 2019-09-17 23:20:26 +0300 |
commit | 583d8d1c28cf0b96810a81dfb7c76a3c1140d764 (patch) | |
tree | d41249e5762a1f75192969ef600c18e33da3413d /core/Http.php | |
parent | 93b7f61ae9ca341f889c1ac36c7fb6bc11ff7736 (diff) |
Let plugins hook and listen into http requests (#14877)
* Let plugins hook and listen into http requests
* fix test
Diffstat (limited to 'core/Http.php')
-rw-r--r-- | core/Http.php | 127 |
1 files changed, 95 insertions, 32 deletions
diff --git a/core/Http.php b/core/Http.php index b7b93e5e89..4525b21365 100644 --- a/core/Http.php +++ b/core/Http.php @@ -135,8 +135,8 @@ class Http * @param array|string $requestBody If $httpMethod is 'POST' this may accept an array of variables or a string that needs to be posted * @param array $additionalHeaders List of additional headers to set for the request * - * @throws Exception - * @return bool true (or string/array) on success; false on HTTP response error code (1xx or 4xx) + * @return string|array true (or string/array) on success; false on HTTP response error code (1xx or 4xx) + *@throws Exception */ public static function sendHttpRequestBy( $method = 'socket', @@ -163,8 +163,10 @@ class Http $contentLength = 0; $fileLength = 0; - if (!empty($requestBody) && is_array($requestBody)) { - $requestBody = self::buildQuery($requestBody); + if ( !empty($requestBody ) && is_array($requestBody )) { + $requestBodyQuery = self::buildQuery($requestBody ); + } else { + $requestBodyQuery = $requestBody; } // Piwik services behave like a proxy, so we should act like one. @@ -191,15 +193,70 @@ class Http list($proxyHost, $proxyPort, $proxyUser, $proxyPassword) = self::getProxyConfiguration($aUrl); - $aUrl = trim($aUrl); // other result data $status = null; $headers = array(); + $response = null; $httpAuthIsUsed = !empty($httpUsername) || !empty($httpPassword); + $httpAuth = ''; + if ($httpAuthIsUsed) { + $httpAuth = 'Authorization: Basic ' . base64_encode($httpUsername.':'.$httpPassword) . "\r\n"; + } + + $httpEventParams = array( + 'httpMethod' => $httpMethod, + 'body' => $requestBody, + 'userAgent' => $userAgent, + 'timeout' => $timeout, + 'headers' => array_map('trim', array_filter(array_merge(array( + $rangeHeader, $via, $xff, $httpAuth, $acceptLanguage + ), $additionalHeaders))), + 'verifySsl' => !$acceptInvalidSslCertificate, + 'destinationPath' => $destinationPath + ); + + /** + * Triggered to send an HTTP request. Allows plugins to resolve the HTTP request themselves or to find out + * when an HTTP request is triggered to log this information for example to a monitoring tool. + * + * @param string $url The URL that needs to be requested + * @param array $params HTTP params like + * - 'httpMethod' (eg GET, POST, ...), + * - 'body' the request body if the HTTP method needs to be posted + * - 'userAgent' + * - 'timeout' After how many seconds a request should time out + * - 'headers' An array of header strings like array('Accept-Language: en', '...') + * - 'verifySsl' A boolean whether SSL certificate should be verified + * - 'destinationPath' If set, the response of the HTTP request should be saved to this file + * @param string &$response A plugin listening to this event should assign the HTTP response it received to this variable, for example "{value: true}" + * @param string &$status A plugin listening to this event should assign the HTTP status code it received to this variable, for example "200" + * @param array &$headers A plugin listening to this event should assign the HTTP headers it received to this variable, eg array('Content-Length' => '5') + */ + Piwik::postEvent('Http.sendHttpRequest', array($aUrl, $httpEventParams, &$response, &$status, &$headers)); + + if ($response !== null || $status !== null || !empty($headers)) { + // was handled by event above... + /** + * described below + * @ignore + */ + Piwik::postEvent('Http.sendHttpRequest.end', array($aUrl, $httpEventParams, &$response, &$status, &$headers)); + + if ($getExtendedInfo) { + return array( + 'status' => $status, + 'headers' => $headers, + 'data' => $response + ); + } else { + return trim($response); + } + } + if ($method == 'socket') { if (!self::isSocketEnabled()) { // can be triggered in tests @@ -257,11 +314,6 @@ class Http throw new Exception("Error while connecting to: $host. Please try again later. $errstr"); } - $httpAuth = ''; - if ($httpAuthIsUsed) { - $httpAuth = 'Authorization: Basic ' . base64_encode($httpUsername.':'.$httpPassword) . "\r\n"; - } - // send HTTP request header $requestHeader .= "Host: $host" . ($port != 80 && ('https' == $url['scheme'] && $port != 443) ? ':' . $port : '') . "\r\n" @@ -276,10 +328,10 @@ class Http . "Connection: close\r\n"; fwrite($fsock, $requestHeader); - if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { - fwrite($fsock, self::buildHeadersForPost($requestBody)); + if (strtolower($httpMethod) === 'post' && !empty($requestBodyQuery )) { + fwrite($fsock, self::buildHeadersForPost($requestBodyQuery )); fwrite($fsock, "\r\n"); - fwrite($fsock, $requestBody); + fwrite($fsock, $requestBodyQuery ); } else { fwrite($fsock, "\r\n"); } @@ -368,7 +420,7 @@ class Http $httpMethod, $httpUsername, $httpPassword, - $requestBody, + $requestBodyQuery, $additionalHeaders ); } @@ -424,11 +476,6 @@ class Http $default_socket_timeout = @ini_get('default_socket_timeout'); @ini_set('default_socket_timeout', $timeout); - $httpAuth = ''; - if ($httpAuthIsUsed) { - $httpAuth = 'Authorization: Basic ' . base64_encode($httpUsername.':'.$httpPassword) . "\r\n"; - } - $ctx = null; if (function_exists('stream_context_create')) { $stream_options = array( @@ -453,12 +500,12 @@ class Http } } - if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { - $postHeader = self::buildHeadersForPost($requestBody); + if (strtolower($httpMethod) === 'post' && !empty($requestBodyQuery )) { + $postHeader = self::buildHeadersForPost($requestBodyQuery ); $postHeader .= "\r\n"; $stream_options['http']['method'] = 'POST'; $stream_options['http']['header'] .= $postHeader; - $stream_options['http']['content'] = $requestBody; + $stream_options['http']['content'] = $requestBodyQuery; } $ctx = stream_context_create($stream_options); @@ -543,9 +590,9 @@ class Http @curl_setopt($ch, CURLOPT_NOBODY, true); } - if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { + if (strtolower($httpMethod) === 'post' && !empty($requestBodyQuery )) { curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody); + curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBodyQuery ); } if (!empty($httpUsername) && !empty($httpPassword)) { @@ -634,6 +681,25 @@ class Http return true; } + /** + * Triggered when an HTTP request finished. A plugin can for example listen to this and alter the response, + * status code, or finish a timer in case the plugin is measuring how long it took to execute the request + * + * @param string $url The URL that needs to be requested + * @param array $params HTTP params like + * - 'httpMethod' (eg GET, POST, ...), + * - 'body' the request body if the HTTP method needs to be posted + * - 'userAgent' + * - 'timeout' After how many seconds a request should time out + * - 'headers' An array of header strings like array('Accept-Language: en', '...') + * - 'verifySsl' A boolean whether SSL certificate should be verified + * - 'destinationPath' If set, the response of the HTTP request should be saved to this file + * @param string &$response The response of the HTTP request, for example "{value: true}" + * @param string &$status The returned HTTP status code, for example "200" + * @param array &$headers The returned headers, eg array('Content-Length' => '5') + */ + Piwik::postEvent('Http.sendHttpRequest.end', array($aUrl, $httpEventParams, &$response, &$status, &$headers)); + if (!$getExtendedInfo) { return trim($response); } else { @@ -862,15 +928,12 @@ class Http $headers[$name] = trim($value); /** - * With HTTP/2 Cloudflare is passing headers in lowercase (e.g. 'content-type' instead of 'Content-Type') - * which breaks any code which uses the header data. + * With HTTP/2 Cloudflare is passing headers in lowercase (e.g. 'content-type' instead of 'Content-Type') + * which breaks any code which uses the header data. */ - if (version_compare(PHP_VERSION, '5.5.16', '>=')) { - // Passing a second arg to ucwords is not supported by older versions of PHP - $camelName = ucwords($name, '-'); - if ($camelName !== $name) { - $headers[$camelName] = trim($value); - } + $camelName = ucwords($name, '-'); + if ($camelName !== $name) { + $headers[$camelName] = trim($value); } } |