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

Reply via email to