Ejegg has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/334453 )
Change subject: Add cURL wrapper
......................................................................
Add cURL wrapper
Moves the curl_transaction logic down into SmashPig, in preparation
for making API calls without using processor SDKs
Change-Id: I6710c327c39866367b169922df4a25df3e4127c4
---
A Core/Http/OutboundRequest.php
1 file changed, 177 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/SmashPig
refs/changes/53/334453/1
diff --git a/Core/Http/OutboundRequest.php b/Core/Http/OutboundRequest.php
new file mode 100644
index 0000000..f76f32c
--- /dev/null
+++ b/Core/Http/OutboundRequest.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace SmashPig\Core\Http;
+
+use HttpRuntimeException;
+use SmashPig\Core\Context;
+use SmashPig\Core\Logging\Logger;
+
+/**
+ * Wraps cURL access, handles retries
+ */
+class OutboundRequest {
+ protected $headers;
+ protected $config;
+ protected $method;
+ protected $curlDebugLog;
+
+ public function __construct( $url, $method = 'GET' ) {
+ $this->config = Context::get()->getConfiguration();
+ $this->url = $url;
+ $this->method = $method;
+ }
+
+ public function setHeader( $name, $value ) {
+ $this->headers[$name] = $value;
+ }
+
+ public function getHeaders() {
+ return $this->headers;
+ }
+
+ public function execute( $data = null ) {
+ $loopCount = $this->config->val( 'api_repeat' );
+
+ Logger::info( "Initiating cURL" );
+ $ch = curl_init();
+ // Always capture the cURL output
+ $curlDebugLog = fopen( 'php://temp', 'r+' );
+
+ $curlOptions = $this->getCurlOptions( $data );
+ curl_setopt_array( $ch, $curlOptions );
+ // TODO: log timing
+
+ $tries = 0;
+ do {
+ Logger::info(
+ "Preparing to send {$this->method} request to
{$this->url}"
+ );
+
+ // Execute the cURL operation
+ $response = curl_exec( $ch );
+
+ // Always read the verbose output
+ rewind( $curlDebugLog );
+ $logged = fread( $curlDebugLog, 4096 );
+
+ if ( $response !== false ) {
+ // The cURL operation was at least successful,
what happened in it?
+ Logger::debug( "cURL verbose logging: $logged"
);
+
+ $headers = curl_getinfo( $ch );
+ $httpCode = $headers['http_code'];
+
+ switch ( $httpCode ) {
+ case 200: // Everything is AWESOME
+ $continue = false;
+
+ Logger::debug( "Successful
transaction to $this->url" );
+ break;
+
+ case 400: // Oh noes! Bad request..
BAD CODE, BAD BAD CODE!
+ $continue = false;
+ Logger::error( "{$this->url}
returned (400) BAD REQUEST: $response" );
+ break;
+
+ case 403: // Hmm, forbidden? Maybe if
we ask it nicely again...
+ $continue = true;
+ Logger::alert( "{$this->url}
returned (403) FORBIDDEN: $response" );
+ break;
+
+ default: // No clue what happened...
break out and log it
+ $continue = false;
+ Logger::error( "{$this->url}
failed remotely and returned ($httpCode): $response" );
+ break;
+ }
+ } else {
+ // Well the cURL transaction failed for some
reason or another. Try again!
+ $continue = true;
+
+ $errno = curl_errno( $ch );
+ $err = curl_error( $ch );
+
+ Logger::alert(
+ "cURL transaction to {$this->url}
failed: ($errno) $err. " .
+ "cURL verbose logging: $logged"
+ );
+ }
+ $tries++;
+ if ( $tries >= $loopCount ) {
+ $continue = false;
+ }
+ if ( $continue ) {
+ // If we're going to try again, reset the debug
log.
+ // TODO: log timing for this particular curl
attempt
+ rewind( $curlDebugLog );
+ }
+ } while ( $continue ); // End while cURL transaction hasn't
returned something useful
+
+ if ( $response === false ) {
+ // no valid response after multiple tries
+ throw new HttpRuntimeException(
+ "{$this->method} request to {$this->url} failed
$loopCount times."
+ );
+ }
+ // Clean up and return
+ curl_close( $ch );
+ fclose( $curlDebugLog );
+
+ return $this->parseResponse( $response );
+ }
+
+ protected function getCurlOptions( $data ) {
+ $options = array(
+ CURLOPT_URL => $this->url,
+ CURLOPT_USERAGENT => $this->config->val( 'user-agent' ),
+ CURLOPT_HEADER => 1,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_TIMEOUT => $this->config->val( 'curl-timeout' ),
+ CURLOPT_FOLLOWLOCATION => 0,
+ CURLOPT_SSL_VERIFYPEER => 1,
+ CURLOPT_SSL_VERIFYHOST => 2,
+ CURLOPT_FORBID_REUSE => true,
+ CURLOPT_VERBOSE => true
+ );
+ if ( $this->curlDebugLog ) {
+ $options[CURLOPT_STDERR] = $this->curlDebugLog;
+ }
+ $headers = $this->getHeaders();
+ switch( $this->method ) {
+ case 'PUT':
+ $options[CURLOPT_PUT] = 1;
+ break;
+ case 'DELETE':
+ case 'HEAD':
+ $options[CURLOPT_CUSTOMREQUEST] = $this->method;
+ break;
+ case 'POST':
+ $options[CURLOPT_POST] = 1;
+ break;
+ default:
+ break;
+ }
+ if ( $data !== null ) {
+ $headers[] = 'Content-Length: ' . strlen( $data );
+ $options[CURLOPT_POSTFIELDS] = $data;
+ }
+ $options[CURLOPT_HTTPHEADER] = $headers;
+ return $options;
+ }
+
+ protected function parseResponse( $response ) {
+ // Thanks StackOverflow!
+ list( $headerString, $body ) = explode( "\r\n\r\n", $response,
2 );
+ $headerLines = explode( "\r\n", $headerString );
+ $headers = array();
+ foreach( $headerLines as $line ) {
+ if ( strstr( $line, ': ' ) ) {
+ $parts = explode( ': ', $line, 2 );
+ $headers[$parts[0]] = $parts[1];
+ }
+ }
+ return array(
+ 'headers' => $headers,
+ 'body' => $body
+ );
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/334453
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6710c327c39866367b169922df4a25df3e4127c4
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Ejegg <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits