MediaWiki  REL1_24
SquidUpdate.php
Go to the documentation of this file.
00001 <?php
00028 class SquidUpdate {
00033     protected $urlArr;
00034 
00039     public function __construct( $urlArr = array(), $maxTitles = false ) {
00040         global $wgMaxSquidPurgeTitles;
00041         if ( $maxTitles === false ) {
00042             $maxTitles = $wgMaxSquidPurgeTitles;
00043         }
00044 
00045         // Remove duplicate URLs from list
00046         $urlArr = array_unique( $urlArr );
00047         if ( count( $urlArr ) > $maxTitles ) {
00048             // Truncate to desired maximum URL count
00049             $urlArr = array_slice( $urlArr, 0, $maxTitles );
00050         }
00051         $this->urlArr = $urlArr;
00052     }
00053 
00063     public static function newFromLinksTo( Title $title ) {
00064         global $wgMaxSquidPurgeTitles;
00065         wfProfileIn( __METHOD__ );
00066 
00067         # Get a list of URLs linking to this page
00068         $dbr = wfGetDB( DB_SLAVE );
00069         $res = $dbr->select( array( 'links', 'page' ),
00070             array( 'page_namespace', 'page_title' ),
00071             array(
00072                 'pl_namespace' => $title->getNamespace(),
00073                 'pl_title' => $title->getDBkey(),
00074                 'pl_from=page_id' ),
00075             __METHOD__ );
00076         $blurlArr = $title->getSquidURLs();
00077         if ( $res->numRows() <= $wgMaxSquidPurgeTitles ) {
00078             foreach ( $res as $BL ) {
00079                 $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title );
00080                 $blurlArr[] = $tobj->getInternalURL();
00081             }
00082         }
00083 
00084         wfProfileOut( __METHOD__ );
00085 
00086         return new SquidUpdate( $blurlArr );
00087     }
00088 
00096     public static function newFromTitles( $titles, $urlArr = array() ) {
00097         global $wgMaxSquidPurgeTitles;
00098         $i = 0;
00100         foreach ( $titles as $title ) {
00101             $urlArr[] = $title->getInternalURL();
00102             if ( $i++ > $wgMaxSquidPurgeTitles ) {
00103                 break;
00104             }
00105         }
00106 
00107         return new SquidUpdate( $urlArr );
00108     }
00109 
00114     public static function newSimplePurge( Title $title ) {
00115         $urlArr = $title->getSquidURLs();
00116 
00117         return new SquidUpdate( $urlArr );
00118     }
00119 
00123     public function doUpdate() {
00124         self::purge( $this->urlArr );
00125     }
00126 
00135     public static function purge( $urlArr ) {
00136         global $wgSquidServers, $wgHTCPRouting;
00137 
00138         if ( !$urlArr ) {
00139             return;
00140         }
00141 
00142         wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
00143 
00144         if ( $wgHTCPRouting ) {
00145             self::HTCPPurge( $urlArr );
00146         }
00147 
00148         wfProfileIn( __METHOD__ );
00149 
00150         // Remove duplicate URLs
00151         $urlArr = array_unique( $urlArr );
00152         // Maximum number of parallel connections per squid
00153         $maxSocketsPerSquid = 8;
00154         // Number of requests to send per socket
00155         // 400 seems to be a good tradeoff, opening a socket takes a while
00156         $urlsPerSocket = 400;
00157         $socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
00158         if ( $socketsPerSquid > $maxSocketsPerSquid ) {
00159             $socketsPerSquid = $maxSocketsPerSquid;
00160         }
00161 
00162         $pool = new SquidPurgeClientPool;
00163         $chunks = array_chunk( $urlArr, ceil( count( $urlArr ) / $socketsPerSquid ) );
00164         foreach ( $wgSquidServers as $server ) {
00165             foreach ( $chunks as $chunk ) {
00166                 $client = new SquidPurgeClient( $server );
00167                 foreach ( $chunk as $url ) {
00168                     $client->queuePurge( $url );
00169                 }
00170                 $pool->addClient( $client );
00171             }
00172         }
00173         $pool->run();
00174 
00175         wfProfileOut( __METHOD__ );
00176     }
00177 
00184     public static function HTCPPurge( $urlArr ) {
00185         global $wgHTCPRouting, $wgHTCPMulticastTTL;
00186         wfProfileIn( __METHOD__ );
00187 
00188         // HTCP CLR operation
00189         $htcpOpCLR = 4;
00190 
00191         // @todo FIXME: PHP doesn't support these socket constants (include/linux/in.h)
00192         if ( !defined( "IPPROTO_IP" ) ) {
00193             define( "IPPROTO_IP", 0 );
00194             define( "IP_MULTICAST_LOOP", 34 );
00195             define( "IP_MULTICAST_TTL", 33 );
00196         }
00197 
00198         // pfsockopen doesn't work because we need set_sock_opt
00199         $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
00200         if ( !$conn ) {
00201             $errstr = socket_strerror( socket_last_error() );
00202             wfDebugLog( 'squid', __METHOD__ .
00203                 ": Error opening UDP socket: $errstr" );
00204             wfProfileOut( __METHOD__ );
00205 
00206             return;
00207         }
00208 
00209         // Set socket options
00210         socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
00211         if ( $wgHTCPMulticastTTL != 1 ) {
00212             // Set multicast time to live (hop count) option on socket
00213             socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
00214                 $wgHTCPMulticastTTL );
00215         }
00216 
00217         // Remove duplicate URLs from collection
00218         $urlArr = array_unique( $urlArr );
00219         // Get sequential trx IDs for packet loss counting
00220         $ids = UIDGenerator::newSequentialPerNodeIDs(
00221             'squidhtcppurge', 32, count( $urlArr ), UIDGenerator::QUICK_VOLATILE
00222         );
00223 
00224         foreach ( $urlArr as $url ) {
00225             if ( !is_string( $url ) ) {
00226                 wfProfileOut( __METHOD__ );
00227                 throw new MWException( 'Bad purge URL' );
00228             }
00229             $url = self::expand( $url );
00230             $conf = self::getRuleForURL( $url, $wgHTCPRouting );
00231             if ( !$conf ) {
00232                 wfDebugLog( 'squid', __METHOD__ .
00233                     "No HTCP rule configured for URL {$url} , skipping" );
00234                 continue;
00235             }
00236 
00237             if ( isset( $conf['host'] ) && isset( $conf['port'] ) ) {
00238                 // Normalize single entries
00239                 $conf = array( $conf );
00240             }
00241             foreach ( $conf as $subconf ) {
00242                 if ( !isset( $subconf['host'] ) || !isset( $subconf['port'] ) ) {
00243                     wfProfileOut( __METHOD__ );
00244                     throw new MWException( "Invalid HTCP rule for URL $url\n" );
00245                 }
00246             }
00247 
00248             // Construct a minimal HTCP request diagram
00249             // as per RFC 2756
00250             // Opcode 'CLR', no response desired, no auth
00251             $htcpTransID = current( $ids );
00252             next( $ids );
00253 
00254             $htcpSpecifier = pack( 'na4na*na8n',
00255                 4, 'HEAD', strlen( $url ), $url,
00256                 8, 'HTTP/1.0', 0 );
00257 
00258             $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
00259             $htcpLen = 4 + $htcpDataLen + 2;
00260 
00261             // Note! Squid gets the bit order of the first
00262             // word wrong, wrt the RFC. Apparently no other
00263             // implementation exists, so adapt to Squid
00264             $htcpPacket = pack( 'nxxnCxNxxa*n',
00265                 $htcpLen, $htcpDataLen, $htcpOpCLR,
00266                 $htcpTransID, $htcpSpecifier, 2 );
00267 
00268             wfDebugLog( 'squid', __METHOD__ .
00269                 "Purging URL $url via HTCP" );
00270             foreach ( $conf as $subconf ) {
00271                 socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
00272                     $subconf['host'], $subconf['port'] );
00273             }
00274         }
00275         wfProfileOut( __METHOD__ );
00276     }
00277 
00292     public static function expand( $url ) {
00293         return wfExpandUrl( $url, PROTO_INTERNAL );
00294     }
00295 
00302     private static function getRuleForURL( $url, $rules ) {
00303         foreach ( $rules as $regex => $routing ) {
00304             if ( $regex === '' || preg_match( $regex, $url ) ) {
00305                 return $routing;
00306             }
00307         }
00308 
00309         return false;
00310     }
00311 }