| @@ -108,14 +108,34 @@ | ||
| 108 | 108 | return (string) apply_filters( 'xspeed_compression_nginx_snippet', $snippet ); |
| 109 | 109 | } |
| 110 | 110 | |
| 111 | 111 | /** |
| 112 | - * Returns true if the response we just received looks GZIP-encoded. | |
| 113 | - * Result is cached in a transient for 1 hour because the probe makes a | |
| 114 | - * full HTTP request to home_url() — too slow to run on every /status hit. | |
| 112 | + * Does the origin actually serve a GZIP-encoded homepage? | |
| 113 | + * | |
| 114 | + * true — proven: the response was gzipped. | |
| 115 | + * false — proven otherwise: we reached the site and it wasn't. | |
| 116 | + * null — no verdict: the loopback never completed (firewalled, | |
| 117 | + * TLS failure, timeout, WAF answering instead of the origin). | |
| 118 | + * | |
| 119 | + * The third state is the point (issue #18). The old signature folded | |
| 120 | + * "couldn't ask" into "answer is no" and cached that for an hour, which | |
| 121 | + * pinned a permanent "GZIP enabled but not active on the server" warning | |
| 122 | + * onto sites whose gzip was demonstrably fine — the loopback was the | |
| 123 | + * only broken thing. Only a *proven* false may nag the user. Mirrors the | |
| 124 | + * inconclusive/active split already used by the rewrite probe. | |
| 125 | + * | |
| 126 | + * A verdict is cached for an hour because the probe costs a full HTTP | |
| 127 | + * request to home_url() — too slow to run on every /status hit. A | |
| 128 | + * non-verdict is cached for a minute only, so a transient blip resolves | |
| 129 | + * itself on the next page load instead of sticking around. | |
| 130 | + * | |
| 131 | + * @return bool|null | |
| 115 | 132 | */ |
| 116 | 133 | public static function probe_active() { |
| 117 | 134 | $cached = get_transient( 'xspeed_gzip_active' ); |
| 135 | + if ( '?' === $cached ) { | |
| 136 | + return null; | |
| 137 | + } | |
| 118 | 138 | if ( false !== $cached ) { |
| 119 | 139 | return '1' === $cached; |
| 120 | 140 | } |
| 121 | 141 | |
| @@ -121,17 +141,43 @@ | ||
| 121 | 141 | |
| 122 | 142 | $res = wp_remote_get( |
| 123 | 143 | home_url( '/' ), |
| 124 | 144 | array( |
| 125 | - 'headers' => array( 'Accept-Encoding' => 'gzip' ), | |
| 126 | - 'timeout' => 3, | |
| 145 | + 'headers' => array( 'Accept-Encoding' => 'gzip' ), | |
| 146 | + // 3s wasn't enough to pull a full homepage on a busy shared | |
| 147 | + // host, and every timeout used to read as "gzip is broken". | |
| 148 | + 'timeout' => 5, | |
| 149 | + // Loopback to our own hostname; staging boxes routinely have | |
| 150 | + // a self-signed or mismatched cert. Same call the sibling | |
| 151 | + // probes make (Browser_Cache::probe_headers_present()). | |
| 152 | + 'sslverify' => false, | |
| 153 | + // Keep the payload exactly as it came off the wire, so the | |
| 154 | + // gzip magic-byte fallback below still has something to | |
| 155 | + // look at if a transport strips Content-Encoding after | |
| 156 | + // decoding. See Cache_Benchmark::measure(). | |
| 157 | + 'decompress' => false, | |
| 127 | 158 | ) |
| 128 | 159 | ); |
| 129 | 160 | |
| 130 | - $active = false; | |
| 131 | - if ( ! is_wp_error( $res ) ) { | |
| 132 | - $enc = wp_remote_retrieve_header( $res, 'content-encoding' ); | |
| 133 | - $active = is_string( $enc ) && false !== stripos( $enc, 'gzip' ); | |
| 161 | + if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) { | |
| 162 | + set_transient( 'xspeed_gzip_active', '?', MINUTE_IN_SECONDS ); | |
| 163 | + return null; | |
| 164 | + } | |
| 165 | + | |
| 166 | + // wp_remote_retrieve_header() hands back a STRING for a single | |
| 167 | + // header but an ARRAY when it appears twice — routine behind a | |
| 168 | + // CDN or proxy. The old is_string() test threw those away and | |
| 169 | + // reported "not gzipped". (Same trap as FBS-82141.) | |
| 170 | + $raw = wp_remote_retrieve_header( $res, 'content-encoding' ); | |
| 171 | + $enc = is_array( $raw ) ? implode( ', ', $raw ) : (string) $raw; | |
| 172 | + | |
| 173 | + $active = false !== stripos( $enc, 'gzip' ); | |
| 174 | + if ( ! $active ) { | |
| 175 | + // Header absent — a transport that decodes and drops it would | |
| 176 | + // look identical to a server that never compressed. The raw | |
| 177 | + // body settles it: a gzip stream starts with 0x1f 0x8b. | |
| 178 | + $body = wp_remote_retrieve_body( $res ); | |
| 179 | + $active = is_string( $body ) && 0 === strncmp( $body, "\x1f\x8b", 2 ); | |
| 134 | 180 | } |
| 135 | 181 | |
| 136 | 182 | set_transient( 'xspeed_gzip_active', $active ? '1' : '0', HOUR_IN_SECONDS ); |
| 137 | 183 | return $active; |