| @@ -1,9 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * XSpeed_Redis_Client — a minimal, dependency-free Redis client. |
| 4 | 4 | * |
| 5 | - * Speaks the Redis wire protocol (RESP) directly over a TCP socket. It exists | |
| 5 | + * Speaks the Redis wire protocol (RESP) directly over a TCP or unix socket. It exists | |
| 6 | 6 | * so xSpeed's object cache can talk to Redis WITHOUT the phpredis extension and |
| 7 | 7 | * WITHOUT bundling a heavyweight library (Predis ships 700+ files for cluster / |
| 8 | 8 | * sentinel / pub-sub / transactions we never use). This implements exactly the |
| 9 | 9 | * commands the object cache needs and nothing more: |
| @@ -49,8 +49,30 @@ | ||
| 49 | 49 | $this->persistent = $persistent; |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | 52 | /** |
| 53 | + * The unix socket path a Redis host names, or '' for a network host. | |
| 54 | + * | |
| 55 | + * Hosts that run Redis on a socket only (Hetzner managed servers, shared | |
| 56 | + * LiteSpeed hosting) hand out a path rather than an address, written as | |
| 57 | + * `/run/redis/redis.sock`, `unix:/run/redis/redis.sock` (Hetzner's own | |
| 58 | + * docs) or `unix:///run/redis/redis.sock`. A socket has no port, so the | |
| 59 | + * caller ignores the configured one when this returns a path. (#566) | |
| 60 | + * | |
| 61 | + * The drop-in carries a copy as xspeed_oc_redis_socket(), because it loads | |
| 62 | + * before this class can; ObjectCacheSocketTest runs both over the same | |
| 63 | + * inputs so they cannot drift. | |
| 64 | + */ | |
| 65 | + public static function socket_path( string $host ): string { | |
| 66 | + $host = trim( $host ); | |
| 67 | + if ( 0 === stripos( $host, 'unix:' ) ) { | |
| 68 | + // unix:///path and unix:/path both name /path. | |
| 69 | + $host = '/' . ltrim( substr( $host, 5 ), '/' ); | |
| 70 | + } | |
| 71 | + return ( '' !== $host && '/' === $host[0] && '/' !== $host ) ? $host : ''; | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 53 | 75 | * Open the socket. Returns true on success. Never throws — callers check |
| 54 | 76 | * the boolean and fall back to a non-persistent cache on failure. |
| 55 | 77 | */ |
| 56 | 78 | public function connect(): bool { |
| @@ -56,11 +78,12 @@ | ||
| 56 | 78 | public function connect(): bool { |
| 57 | 79 | $flags = STREAM_CLIENT_CONNECT | ( $this->persistent ? STREAM_CLIENT_PERSISTENT : 0 ); |
| 58 | 80 | $errno = 0; |
| 59 | 81 | $errstr = ''; |
| 82 | + $path = self::socket_path( $this->host ); | |
| 60 | 83 | // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.PHP.NoSilencedErrors.Discouraged -- A raw stream socket is the only way to speak the Redis protocol; WP_Filesystem cannot open TCP sockets. Errors are captured via $errno/$errstr and surfaced as a boolean. |
| 61 | 84 | $sock = @stream_socket_client( |
| 62 | - "tcp://{$this->host}:{$this->port}", | |
| 85 | + '' !== $path ? "unix://{$path}" : "tcp://{$this->host}:{$this->port}", | |
| 63 | 86 | $errno, |
| 64 | 87 | $errstr, |
| 65 | 88 | $this->timeout, |
| 66 | 89 | $flags |